diff --git a/doc/HOOKS.md b/doc/HOOKS.md --- a/doc/HOOKS.md +++ b/doc/HOOKS.md @@ -22,8 +22,8 @@ The *Toogle Select* sub-menu of the library menu can be used to trigger a hook when there's no imported documents in `path`. Otherwise, you can just tap the directory in the navigation bar. When the hook is triggered, the associated -`program` is executed as a background process. It will receive the directory -path, wifi and online statuses (*true* or *false*) as arguments. +`program` is executed as a background process. It will receive the library path, +directory path, wifi and online statuses (*true* or *false*) as arguments. A fetcher can use its standard output (resp. standard input) to send events to (resp. receive events from) *Plato*. An event is a JSON object with a required @@ -37,23 +37,21 @@ // Add a document to the current library. `info` is the camel cased JSON version // of the `Info` structure defined in `src/metadata.rs`. {"type": "addDocument", "info": OBJECT} +// Remove a document from the current library. +{"type": "removeDocument", "path": STRING} // Enable or disable the WiFi. {"type": "setWifi", "enable": BOOL} // Search for books inside `path` matching `query` and sort the results by `sortBy`. {"type": "search", "path": STRING, "query": STRING, "sortBy": [STRING, BOOL]} -// Import new entries and update existing entries in the current library. -{"type": "import"} -// Remove entries with dangling paths from the current library. -{"type": "cleanUp"} ``` The events that can be read from standard input are: ``` -// Sent in response to `search`. `path` is the path of the library -// that was searched for. `results` is an array of *Info* objects. -{"type": "search": "path": STRING, "results": ARRAY} +// Sent in response to `search`. +// `results` is an array of *Info* objects. +{"type": "search": "results": ARRAY} // Sent to all the fetchers when the network becomes available. {"type": "network", "status": "up"} ``` diff --git a/src/app.rs b/src/app.rs --- a/src/app.rs +++ b/src/app.rs @@ -1045,9 +1045,8 @@ }, Event::CheckFetcher(..) | Event::FetcherAddDocument(..) | - Event::FetcherSearch { .. } | - Event::FetcherCleanUp(..) | - Event::FetcherImport(..) if !view.is::() => { + Event::FetcherRemoveDocument(..) | + Event::FetcherSearch { .. } if !view.is::() => { if let Some(entry) = history.get_mut(0).filter(|entry| entry.view.is::()) { let (tx, _rx) = mpsc::channel(); entry.view.handle_event(&evt, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); diff --git a/src/emulator.rs b/src/emulator.rs --- a/src/emulator.rs +++ b/src/emulator.rs @@ -507,9 +507,8 @@ Event::Device(DeviceEvent::NetUp) | Event::CheckFetcher(..) | Event::FetcherAddDocument(..) | - Event::FetcherSearch { .. } | - Event::FetcherCleanUp(..) | - Event::FetcherImport(..) if !view.is::() => { + Event::FetcherRemoveDocument(..) | + Event::FetcherSearch { .. } if !view.is::() => { if let Some(home) = history.get_mut(0).filter(|view| view.is::()) { let (tx, _rx) = mpsc::channel(); home.handle_event(&evt, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); diff --git a/src/fetcher.rs b/src/fetcher.rs --- a/src/fetcher.rs +++ b/src/fetcher.rs @@ -117,6 +117,8 @@ fn main() -> Result<(), Error> { let mut args = env::args().skip(1); + let library_path = PathBuf::from(args.next() + .ok_or_else(|| format_err!("Missing argument: library path."))?); let save_path = PathBuf::from(args.next() .ok_or_else(|| format_err!("Missing argument: save path."))?); let wifi = args.next() @@ -182,10 +184,6 @@ let mut archivals_count = 0; if let Ok(event) = serde_json::from_str::(&line) { - let library_path = event.get("path") - .and_then(JsonValue::as_str) - .map(PathBuf::from) - .unwrap_or_else(|| save_path.clone()); if let Some(results) = event.get("results").and_then(JsonValue::as_array) { let message = if results.is_empty() { "No finished articles.".to_string() @@ -230,13 +228,14 @@ } if settings.remove_finished { - if let Some(relat) = entry.pointer("/file/path") - .and_then(JsonValue::as_str) { - let path = library_path.join(relat); - match fs::remove_file(&path) { - Ok(()) => session.removals_count = session.removals_count.wrapping_add(1), - Err(e) => eprintln!("Can't remove {}: {}", path.display(), e), - } + if let Some(path) = entry.pointer("/file/path") + .and_then(JsonValue::as_str) { + let event = json!({ + "type": "removeDocument", + "path": path, + }); + println!("{}", event); + session.removals_count = session.removals_count.wrapping_add(1) } } @@ -272,10 +271,6 @@ "message": &message, }); println!("{}", event); - if removals_count > 0 { - let event = json!({"type": "cleanUp"}); - println!("{}", event); - } } } } @@ -407,30 +402,32 @@ session.downloads_count = session.downloads_count.wrapping_add(1); - let file_info = json!({ - "path": epub_path.to_str().unwrap_or(""), - "kind": "epub", - "size": file.metadata().ok() - .map_or(0, |m| m.len()), - }); + if let Ok(path) = epub_path.strip_prefix(&library_path) { + let file_info = json!({ + "path": path, + "kind": "epub", + "size": file.metadata().ok() + .map_or(0, |m| m.len()), + }); - let info = json!({ - "title": title, - "author": author, - "year": year, - "identifier": id.to_string(), - "added": updated_at.with_timezone(&Local) - .format("%Y-%m-%d %H:%M:%S") - .to_string(), - "file": file_info, - }); + let info = json!({ + "title": title, + "author": author, + "year": year, + "identifier": id.to_string(), + "added": updated_at.with_timezone(&Local) + .format("%Y-%m-%d %H:%M:%S") + .to_string(), + "file": file_info, + }); - let event = json!({ - "type": "addDocument", - "info": &info, - }); + let event = json!({ + "type": "addDocument", + "info": &info, + }); - println!("{}", event); + println!("{}", event); + } } } diff --git a/src/view/mod.rs b/src/view/mod.rs --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -314,9 +314,8 @@ CloseSub(ViewId), Search(String), SearchResult(usize, Vec), - FetcherCleanUp(u32), - FetcherImport(u32), FetcherAddDocument(u32, Box), + FetcherRemoveDocument(u32, PathBuf), FetcherSearch { id: u32, path: Option, diff --git a/src/view/home/mod.rs b/src/view/home/mod.rs --- a/src/view/home/mod.rs +++ b/src/view/home/mod.rs @@ -978,13 +978,10 @@ } } - fn add_document(&mut self, mut info: Info, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { - if let Ok(path) = info.file.path.strip_prefix(&context.library.home) { - info.file.path = path.to_path_buf(); - context.library.add_document(info); - self.sort(false, hub, rq, context); - self.refresh_visibles(true, false, hub, rq, context); - } + fn add_document(&mut self, info: Info, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { + context.library.add_document(info); + self.sort(false, hub, rq, context); + self.refresh_visibles(true, false, hub, rq, context); } fn set_status(&mut self, path: &Path, status: SimpleStatus, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { @@ -1166,8 +1163,9 @@ } fn insert_fetcher(&mut self, hook: &Hook, hub: &Hub, context: &Context) { - let dir = context.library.home.join(&hook.path); - match self.spawn_child(&dir, &hook.program, context.settings.wifi, context.online, hub) { + let library_path = &context.library.home; + let save_path = context.library.home.join(&hook.path); + match self.spawn_child(library_path, &save_path, &hook.program, context.settings.wifi, context.online, hub) { Ok(process) => { let mut sort_method = hook.sort_method; let mut first_column = hook.first_column; @@ -1183,20 +1181,21 @@ hub.send(Event::Select(EntryId::SecondColumn(second_column))).ok(); } self.background_fetchers.insert(process.id(), - Fetcher { path: hook.path.clone(), full_path: dir, process, + Fetcher { path: hook.path.clone(), full_path: save_path, process, sort_method, first_column, second_column }); }, Err(e) => eprintln!("Can't spawn child: {}.", e), } } - fn spawn_child(&mut self, dir: &Path, program: &PathBuf, wifi: bool, online: bool, hub: &Hub) -> Result { + fn spawn_child(&mut self, library_path: &Path, save_path: &Path, program: &Path, wifi: bool, online: bool, hub: &Hub) -> Result { let path = program.canonicalize()?; let parent = path.parent() .unwrap_or_else(|| Path::new("")); let mut process = Command::new(&path) .current_dir(parent) - .arg(dir) + .arg(library_path) + .arg(save_path) .arg(wifi.to_string()) .arg(online.to_string()) .stdin(Stdio::piped()) @@ -1232,6 +1231,12 @@ hub2.send(Event::FetcherAddDocument(id, Box::new(info))).ok(); } }, + Some("removeDocument") => { + if let Some(path) = event.get("path") + .and_then(JsonValue::as_str) { + hub2.send(Event::FetcherRemoveDocument(id, PathBuf::from(path))).ok(); + } + }, Some("search") => { let path = event.get("path") .and_then(JsonValue::as_str) @@ -1243,12 +1248,6 @@ .map(ToString::to_string) .and_then(|v| serde_json::from_str(&v).ok()); hub2.send(Event::FetcherSearch { id, path, query, sort_by }).ok(); - }, - Some("cleanUp") => { - hub2.send(Event::FetcherCleanUp(id)).ok(); - }, - Some("import") => { - hub2.send(Event::FetcherImport(id)).ok(); }, _ => (), } @@ -1404,11 +1403,11 @@ self.load_library(index, hub, rq, context); true }, - Event::Select(EntryId::Import) | Event::FetcherImport(_) => { + Event::Select(EntryId::Import) => { self.import(hub, rq, context); true }, - Event::Select(EntryId::CleanUp) | Event::FetcherCleanUp(_) => { + Event::Select(EntryId::CleanUp) => { self.clean_up(hub, rq, context); true }, @@ -1417,8 +1416,7 @@ true }, Event::FetcherAddDocument(_, ref info) => { - let info2 = info.clone(); - self.add_document(*info2, hub, rq, context); + self.add_document(*info.clone(), hub, rq, context); true }, Event::Select(EntryId::SetStatus(ref path, status)) => { @@ -1489,7 +1487,7 @@ } true }, - Event::Select(EntryId::Remove(ref path)) => { + Event::Select(EntryId::Remove(ref path)) | Event::FetcherRemoveDocument(_, ref path) => { self.remove(path, hub, rq, context) .map_err(|e| eprintln!("{}", e)) .ok(); @@ -1580,7 +1578,6 @@ if let Some(fetcher) = self.background_fetchers.get_mut(&id) { if let Some(stdin) = fetcher.process.stdin.as_mut() { writeln!(stdin, "{}", json!({"type": "search", - "path": context.library.home, "results": files})).ok(); } }