11use std:: {
2- env:: { self } , fs:: { self , File } , io:: { self , BufReader , BufWriter , Read } , path:: Path , process:: { exit, ExitCode } , str:: { self }
2+ env:: { self } , fs:: { self , File } , io:: { self , BufReader , BufWriter , Read } , path:: Path , process:: { exit, ExitCode } , str:: { self } ,
3+ sync:: { Arc , Mutex } , thread
34} ;
45
56use xml:: { self , reader:: XmlEvent , EventReader } ;
@@ -10,7 +11,7 @@ mod lexer;
1011mod server;
1112mod model;
1213
13- use crate :: model:: { InMemoryModel , Model , SqliteModel } ;
14+ use crate :: model:: * ;
1415
1516/* Parse all the text (Character Events) from the XML File */
1617fn parse_xml_file ( file_path : & Path ) -> Result < String , ( ) > {
@@ -36,6 +37,7 @@ fn parse_xml_file(file_path: &Path) -> Result<String, ()> {
3637 Ok ( content)
3738}
3839
40+ /* Parse all the text from the TXT File */
3941fn parse_txt_file ( file_path : & Path ) -> Result < String , ( ) > {
4042 let mut content = String :: new ( ) ;
4143 let mut file = File :: open ( file_path) . map_err ( |err| {
@@ -81,30 +83,37 @@ fn save_model_as_json(model: &InMemoryModel, index_path: &str) -> Result<(), ()>
8183 Ok ( ( ) )
8284}
8385
84- /* Indexes a folder as a json file and adds to model */
85- fn append_folder_to_model ( dir_path : & Path , model : & mut dyn Model ) -> Result < ( ) , ( ) > {
86+ /* Indexes a folder as a json file and adds to model, Processed is the number of file indexed */
87+ fn append_folder_to_model ( dir_path : & Path , model : Arc < Mutex < InMemoryModel > > , processed : & mut usize ) -> Result < ( ) , ( ) > {
8688 let dir = fs:: read_dir ( dir_path) . map_err ( |err| {
87- eprintln ! ( "{err }: Failed to read directory {dir_path} as \" {msg }\" " , err = "ERROR" . bold( ) . red( ) ,
89+ eprintln ! ( "{}: Failed to read directory {dir_path} as \" {err }\" " , "ERROR" . bold( ) . red( ) ,
8890 dir_path = dir_path. to_str( ) . unwrap( ) . bold( ) . bright_blue( ) ,
89- msg = err. to_string( ) . red( ) ) ;
91+ err = err. to_string( ) . red( ) ) ;
9092 } ) ?;
9193
9294 ' step: for file in dir {
9395 let file = file. map_err ( |err| {
94- eprintln ! ( "{err }: Failed to read next file in directory {dir_path} as {msg }" , err = "ERROR" . bold( ) . red( ) ,
96+ eprintln ! ( "{}: Failed to read next file in directory {dir_path} as {err }" , "ERROR" . bold( ) . red( ) ,
9597 dir_path = dir_path. to_str( ) . unwrap( ) . bold( ) . bright_blue( ) ,
96- msg = err. to_string( ) . red( ) ) ;
98+ err = err. to_string( ) . red( ) ) ;
9799 } ) ?;
98100
99101 let file_path = file. path ( ) ;
100102 let file_path_str = file_path. to_str ( ) . unwrap ( ) ;
103+
104+ let last_modified = file. metadata ( ) . map_err ( |err| {
105+ eprintln ! ( "{}: Failed to get metadata of file {path} as {err}" , "ERROR" . bold( ) . red( ) , path = file_path_str. bright_blue( ) , err = err. to_string( ) . red( ) ) ;
106+ } ) ?. modified ( ) . map_err ( |err| {
107+ eprintln ! ( "{}: Failed to last modified time of file {path} as {err}" , "ERROR" . bold( ) . red( ) , path = file_path_str. bright_blue( ) , err = err. to_string( ) . red( ) ) ;
108+ } ) . unwrap ( ) ;
109+
101110 let file_ext = file_path. extension ( ) ;
102111
103112 // Skip unsupported files
104113 if let Some ( ext) = file_ext {
105114 const ALLOWED_EXTS : [ & str ; 4 ] = [ "xml" , "xhtml" , "txt" , "md" ] ;
106115 if !ALLOWED_EXTS . contains ( & ext. to_str ( ) . unwrap ( ) ) {
107- println ! ( "{}: Skipping non-XML file {}" , "INFO" . cyan( ) , file_path_str. bright_yellow( ) ) ;
116+ println ! ( "{}: Skipping unsupported file {}" , "INFO" . cyan( ) , file_path_str. bright_yellow( ) ) ;
108117 continue ' step;
109118 }
110119 }
@@ -116,22 +125,33 @@ fn append_folder_to_model(dir_path: &Path, model: &mut dyn Model) -> Result<(),
116125
117126 // Recursively index all the folders
118127 if file_type. is_dir ( ) {
119- append_folder_to_model ( & file_path, model) ?;
128+ // Skip the .git folder
129+ if file_path. file_name ( ) . unwrap ( ) == ".git" {
130+ continue ' step;
131+ }
132+
133+ append_folder_to_model ( & file_path, Arc :: clone ( & model) , processed) ?;
120134 continue ' step;
121135 }
122136
123- println ! ( "{}: Indexing {} ..." , "INFO" . cyan( ) , file_path_str. bright_cyan( ) ) ;
137+ let mut model = model. lock ( ) . unwrap ( ) ;
138+ if model. requires_reindexing ( & file_path, last_modified) ? {
139+ println ! ( "{}: Indexing {} ..." , "INFO" . cyan( ) , file_path_str. bright_cyan( ) ) ;
140+
141+ let content = match parse_file_by_ext ( & file_path) {
142+ Ok ( content) => content. chars ( ) . collect :: < Vec < _ > > ( ) ,
143+ Err ( _) => {
144+ eprintln ! ( "{}: Failed to read xml file {path}" , "ERROR" . bold( ) . red( ) , path = file_path. to_str( ) . unwrap( ) . bright_blue( ) ) ;
145+ continue ' step;
146+ }
147+ } ;
148+
149+ model. add_document ( file_path, & content, last_modified) ?;
150+ * processed += 1 ;
151+ } else {
152+ println ! ( "{}: Ignoring {} as already indexed ..." , "INFO" . cyan( ) , file_path_str. bright_cyan( ) ) ;
153+ }
124154
125- let content = match parse_file_by_ext ( & file_path) {
126- Ok ( content) => content. chars ( ) . collect :: < Vec < _ > > ( ) ,
127- Err ( err) => {
128- eprintln ! ( "{error}: Failed to read xml file {fp}: {msg:?}" , error = "ERROR" . bold( ) . red( ) , fp = file_path. to_str( ) . unwrap( ) . bright_blue( ) , msg = err) ;
129- continue ' step;
130- }
131- } ;
132-
133- // Core Operation
134- model. add_document ( file_path, & content) ?;
135155 }
136156 Ok ( ( ) )
137157}
@@ -155,13 +175,13 @@ fn check_index(index_path: &str) -> Result<(), ()> {
155175const DEFAULT_INDEX_JSON_PATH : & str = "index.json" ;
156176const DEFAULT_INDEX_SQLITE_DB_PATH : & str = "index.db" ;
157177
158-
178+ /* Fetch the InMemory model from an index file */
159179fn fetch_model ( index_path : & str ) -> Result < InMemoryModel , ( ) > {
160180 let index_file = fs:: File :: open ( & index_path) . map_err ( |err| {
161181 eprintln ! ( "{}: Could not open file {file_path} as \" {err}\" " , "ERROR" . bold( ) . red( ) , file_path = index_path. bright_blue( ) , err = err. to_string( ) . red( ) ) ;
162182 } ) . unwrap ( ) ;
163183
164- let model: InMemoryModel = serde_json:: from_reader ( BufReader :: new ( index_file) ) . map_err ( |err| {
184+ let model = serde_json:: from_reader ( BufReader :: new ( index_file) ) . map_err ( |err| {
165185 eprintln ! ( "{}: Serde failed to read {file_path} as \" {err}\" " , "ERROR" . bold( ) . red( ) , file_path = index_path. bright_blue( ) , err = err. to_string( ) . red( ) ) ;
166186 } ) . unwrap ( ) ;
167187
@@ -200,6 +220,27 @@ fn entry() -> Result<(), ()> {
200220 } ) ?;
201221
202222 match subcommand. as_str ( ) {
223+ /*
224+ "reindex" => {
225+ assert!(!use_sqlite_mode, "The sqlite model is deprecated");
226+ let dir_path = args.next().ok_or_else(|| {
227+ usage(&program);
228+ eprintln!("ERROR: no directory is provided for {subcommand} subcommand");
229+ })?;
230+
231+ let index_path = "index.json";
232+ let index_file = File::open(&index_path).map_err(|err| {
233+ eprintln!("ERROR: could not open index file {index_path}: {err}");
234+ })?;
235+ let mut model: InMemoryModel = serde_json::from_reader(index_file).map_err(|err| {
236+ eprintln!("ERROR: could not parse index file {index_path}: {err}");
237+ })?;
238+
239+ append_folder_to_model(Path::new(&dir_path), &mut model)?;
240+ save_model_as_json(&model, index_path)?;
241+ return Ok(());
242+ }
243+
203244 "index" => {
204245 let dir_path = args.next().ok_or_else(|| {
205246 usage(&program);
@@ -224,13 +265,15 @@ fn entry() -> Result<(), ()> {
224265
225266 } else {
226267 let index_path = "index.json";
227- let mut model = InMemoryModel :: default ( ) ;
268+ let mut model = Default ::default();
228269 append_folder_to_model(Path::new(&dir_path), &mut model)?;
229270 save_model_as_json(&model, index_path)?;
230271 }
231272 }
273+ */
232274
233275 "search" => {
276+ assert ! ( !use_sqlite_mode, "Sqlite mode is DEPRACATED." ) ;
234277 let index_path = args. next ( ) . ok_or_else ( || {
235278 usage ( & program) ;
236279 eprintln ! ( "{}: Index file path must provided for {} subcommand." , "ERROR" . bold( ) . red( ) , subcommand. bold( ) . bright_blue( ) ) ;
@@ -269,25 +312,44 @@ fn entry() -> Result<(), ()> {
269312 }
270313
271314 "serve" => {
272- let index_path = args. next ( ) . ok_or_else ( || {
315+ assert ! ( !use_sqlite_mode, "SQLITE mode is DEPRACATED." ) ;
316+ let dir_path = args. next ( ) . ok_or_else ( || {
273317 usage ( & program) ;
274- eprintln ! ( "{}: Index file path must provided for {} subcommand." , "ERROR" . bold( ) . red( ) , subcommand. bold( ) . bright_blue( ) ) ;
318+ eprintln ! ( "{}: No directory path is provided for {} subcommand." , "ERROR" . bold( ) . red( ) , subcommand. bold( ) . bright_blue( ) ) ;
275319 } ) ?;
276-
320+
277321 // Default address
278322 let address = args. next ( ) . unwrap_or ( "127.0.0.1:6969" . to_string ( ) ) ;
279323
280- if use_sqlite_mode {
281- let model = SqliteModel :: open ( Path :: new ( & index_path) ) ?;
282- return server:: start ( & address, & model) ;
283-
284- } else {
285- let model: InMemoryModel = fetch_model ( & index_path) . unwrap_or_else ( |( ) | {
286- eprintln ! ( "{}: Failed to fetch model for {}." , "ERROR" . bold( ) . red( ) , index_path. bright_blue( ) ) ;
324+ // TODO: Figure out index_path based on dir_path
325+ let index_path = DEFAULT_INDEX_JSON_PATH ;
326+ let exists = Path :: new ( index_path) . exists ( ) ;
327+
328+ let model: Arc < Mutex < InMemoryModel > > ;
329+ if exists {
330+ // Fetch already existing model
331+ model = Arc :: new ( Mutex :: new ( fetch_model ( & DEFAULT_INDEX_JSON_PATH ) . unwrap_or_else ( |( ) | {
332+ eprintln ! ( "{}: Failed to fetch model for {}." , "ERROR" . bold( ) . red( ) , DEFAULT_INDEX_JSON_PATH . bright_blue( ) ) ;
287333 exit ( 1 ) ;
334+ } ) ) ) ;
335+ } else {
336+ model = Arc :: new ( Mutex :: new ( Default :: default ( ) ) ) ;
337+ }
338+
339+ {
340+ let model = Arc :: clone ( & model) ;
341+ thread:: spawn ( move || {
342+ let mut processed: usize = 0 as usize ;
343+ append_folder_to_model ( Path :: new ( & dir_path) , Arc :: clone ( & model) , & mut processed) . unwrap ( ) ;
344+
345+ // Save the model only when some files are processed
346+ if processed > 0 {
347+ let model= model. lock ( ) . unwrap ( ) ;
348+ save_model_as_json ( & model, index_path) . unwrap ( ) ;
349+ }
288350 } ) ;
289- return server:: start ( & address, & model) ;
290351 }
352+ return server:: start ( & address, Arc :: clone ( & model) ) ;
291353 }
292354
293355 _ => {
0 commit comments