diff --git a/src/database.rs b/src/database.rs index 9ffcc1d..33ae74b 100644 --- a/src/database.rs +++ b/src/database.rs @@ -52,6 +52,48 @@ impl PaperCategory { sub_categories: vec![], } } + + fn copy_file( + dir: PathBuf, + outside_file: Option, + id: &String, + entry_ref: &mut PaperEntry, + ) -> Result<(), Box> { + if let Some(outside_file) = outside_file { + // check if the file exists + let outside_file_path = PathBuf::from(&outside_file); + if !outside_file_path.exists() { + eprintln!("Error: the file '{}' does not exist.", outside_file); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + "file not found", + ))); + } else { + let file_name = match outside_file_path.extension() { + Some(ext) => { + let ext = ext.to_str().unwrap(); + // if ext != "pdf" { + // eprintln!("Error: the file '{}' is not a PDF file.", outside_file); + // return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, "not a PDF file"))); + // } + format!("{}.{}", &id, ext) + } + _ => id.clone(), + }; + let inside_file = dir.join(file_name); + std::fs::copy(outside_file_path, &inside_file)?; + entry_ref.file = Some( + inside_file + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ); + } + } + Ok(()) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -69,6 +111,10 @@ impl Index { } } +/// Paper entry in the database +/// +/// The fields are now for testing purpose. +/// More fields will be added in the future. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PaperEntry { pub title: Option, @@ -88,6 +134,18 @@ impl PaperEntry { file: None, } } + + pub fn update_metadata(&mut self, paper: &PaperEntry) { + if let Some(title) = paper.title.clone() { + self.title = Some(title); + } + if let Some(authors) = paper.authors.clone() { + self.authors = Some(authors); + } + if let Some(year) = paper.year { + self.year = Some(year); + } + } } type PaperID = String; @@ -137,12 +195,15 @@ impl TpIndex for PaperCategory { } pub trait TpManage { - /// Add a paper entry to the category + /// Add a paper entry /// /// If the paper entry is already in the category, it will be overwritten. fn add(&mut self, id: PaperID, entry: PaperEntry, force: bool) -> Result<(), Box>; - /// Remove a paper entry from the category + /// Edit a paper entry + fn edit(&mut self, id: PaperID, entry: PaperEntry) -> Result<(), Box>; + + /// Remove a paper entry fn remove(&mut self, id: PaperID) -> Result<(), Box>; } @@ -177,6 +238,7 @@ impl TpManage for PaperCategory { force: bool, ) -> Result<(), Box> { // 1. check if the paper entry is already in the category + // (TODO: more efficient way as using the return value to avoid double check) if self.papers.contains_key(&id) && !force { eprintln!( "Error: the paper entry '{}' already exists in the category.", @@ -189,39 +251,7 @@ impl TpManage for PaperCategory { } // 2. copy the file to the category let outside_file = entry.file.clone(); - if let Some(outside_file) = outside_file { - // check if the file exists - let outside_file_path = PathBuf::from(&outside_file); - if !outside_file_path.exists() { - eprintln!("Error: the file '{}' does not exist.", outside_file); - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::NotFound, - "file not found", - ))); - } else { - let file_name = match outside_file_path.extension() { - Some(ext) => { - let ext = ext.to_str().unwrap(); - // if ext != "pdf" { - // eprintln!("Error: the file '{}' is not a PDF file.", outside_file); - // return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, "not a PDF file"))); - // } - format!("{}.{}", &id, ext) - } - _ => id.clone(), - }; - let inside_file = self.dir.join(file_name); - std::fs::copy(outside_file_path, &inside_file)?; - entry.file = Some( - inside_file - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ); - } - } + Self::copy_file(self.dir.clone(), outside_file, &id, &mut entry)?; // 3. add the paper entry to the category self.papers.insert(id, entry); // 4. save the index @@ -232,6 +262,33 @@ impl TpManage for PaperCategory { self.index_to_file(&index) } + fn edit(&mut self, id: PaperID, entry: PaperEntry) -> Result<(), Box> { + match self.papers.get_mut(&id) { + Some(entry_ref) => { + // 1. copy the file to the category + let outside_file = entry.file.clone(); + Self::copy_file(self.dir.clone(), outside_file, &id, entry_ref)?; + entry_ref.update_metadata(&entry); + // 2. save the index + let index = Index { + papers: self.papers.clone(), + sub_categories: self.sub_categories.clone(), + }; + self.index_to_file(&index) + } + None => { + eprintln!( + "Error: the paper entry '{}' does not exist in the database.", + id + ); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + "paper entry not found", + ))); + } + } + } + fn remove(&mut self, id: PaperID) -> Result<(), Box> { match self.papers.remove(&id) { Some(entry) => { @@ -269,6 +326,13 @@ impl TpManage for Database { self.top_category.add(id, entry, force) } + fn edit(&mut self, id: PaperID, entry: PaperEntry) -> Result<(), Box> { + // 1. safety check + _ck_id(&id)?; + // 2. edit from the top category (TODO: check category) + self.top_category.edit(id, entry) + } + fn remove(&mut self, id: PaperID) -> Result<(), Box> { // 1. safety check _ck_id(&id)?; diff --git a/src/manager.rs b/src/manager.rs index bb56dcb..8a9cf30 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -30,7 +30,7 @@ impl Manager { Commands::Activate(_) => self.cmd_activate(), Commands::Add(_) => self.cmd_add(), Commands::Config(_) => self.cmd_config(), - // Commands::Edit(_) => self.cmd_edit(), + Commands::Edit(_) => self.cmd_edit(), Commands::Info(_) => self.cmd_info(), Commands::Init(_) => self.cmd_init(), // Commands::List(_) => self.cmd_list(), @@ -172,16 +172,46 @@ impl Manager { year: args.year.clone(), }; database - .add( - args.id.clone(), - paper, - args.force, - ) + .add(args.id.clone(), paper, args.force) .map_err(|_| ())?; // 5. save the database to the file (TODO) Ok(()) } + pub fn cmd_edit(&self) -> Result<(), ()> { + // 1. get the correct database directory + let database_dir = match &self.config.activated { + Some(activated) => activated.clone(), + None => { + eprintln!("Error: No database is activated."); + return Err(()); + } + }; + // 2. use the Database struct to handle the database + let mut database = Database::new_from_index(database_dir); + // 3. get the paper entry from the user input + let args = match &self.args.cmd { + Commands::Edit(args) => args, + _ => { + assert!( + false, + "Internal Error: This function should only be called in the 'edit' command." + ); + return Err(()); + } + }; + // 4. edit the paper entry from the database + let paper = PaperEntry { + file: args.file.clone(), + title: args.title.clone(), + authors: args.authors.clone(), + year: args.year.clone(), + }; + database.edit(args.id.clone(), paper).map_err(|_| ())?; + // 5. save the database to the file (TODO) + Ok(()) + } + pub fn cmd_info(&self) -> Result<(), ()> { // get the activated database let activated = match &self.config.activated { diff --git a/src/options.rs b/src/options.rs index 43f7457..dc86e3d 100644 --- a/src/options.rs +++ b/src/options.rs @@ -86,9 +86,18 @@ pub struct CommandEditArgs { /// The unique id of the paper to edit #[arg(index = 1)] pub id: String, - /// The file of the paper to edit - #[arg(short, long)] + /// The file of the paper to add + #[arg(short = 'f', long)] pub file: Option, + /// Title of the paper + #[arg(short = 't', long)] + pub title: Option, + /// Author of the paper (one by one with multiple flags) + #[arg(short = 'a', long = "author")] + pub authors: Option>, + /// Year of the paper + #[arg(short = 'y', long)] + pub year: Option, } #[derive(Args, Clone, Debug)]