diff options
Diffstat (limited to 'src/modes/edit/bulkrename.rs')
-rw-r--r-- | src/modes/edit/bulkrename.rs | 308 |
1 files changed, 126 insertions, 182 deletions
diff --git a/src/modes/edit/bulkrename.rs b/src/modes/edit/bulkrename.rs index 8f909cf..41e07c2 100644 --- a/src/modes/edit/bulkrename.rs +++ b/src/modes/edit/bulkrename.rs @@ -1,74 +1,63 @@ use anyhow::{anyhow, Result}; use std::io::{BufRead, Write}; use std::path::{Path, PathBuf}; +use std::sync::mpsc::Sender; +use std::sync::Arc; use std::thread; use std::time::{Duration, SystemTime}; use crate::common::TMP_FOLDER_PATH; use crate::common::{random_name, rename}; -use crate::io::Opener; +use crate::event::FmEvents; use crate::log_info; use crate::log_line; -use crate::modes::mode::BulkAction; -use crate::{impl_content, impl_selectable}; -trait DelTemp { - fn del_temporary_file(&self, temp_file: &Path) -> Result<()> { - std::fs::remove_file(temp_file)?; - Ok(()) - } -} - -trait BulkExecute { - fn execute(&self) -> Result<Option<Vec<PathBuf>>>; -} - -struct Renamer { +struct BulkExecutor { original_filepath: Vec<PathBuf>, temp_file: PathBuf, new_filenames: Vec<String>, + parent_dir: String, } -impl DelTemp for Renamer {} - -impl BulkExecute for Renamer { - fn execute(&self) -> Result<Option<Vec<PathBuf>>> { - let paths = self.rename_all(&self.new_filenames); - self.del_temporary_file(&self.temp_file)?; - paths - } -} - -impl Renamer { - /// Creates a new renamer - fn new(original_filepath: Vec<PathBuf>) -> Self { +impl BulkExecutor { + fn new(original_filepath: Vec<PathBuf>, parent_dir: &str) -> Self { let temp_file = generate_random_filepath(); Self { original_filepath, temp_file, new_filenames: vec![], + parent_dir: parent_dir.to_owned(), } } - /// Rename the files. - /// The tempory file is opened with our `Opener` crate, allowing us - /// to use the default text file editor. - /// Filenames are sanitized before processing. - fn ask_filenames(mut self, opener: &Opener) -> Result<Self> { + fn ask_filenames(self) -> Result<Self> { + create_random_file(&self.temp_file)?; + log_info!("created {temp_file}", temp_file = self.temp_file.display()); self.write_original_names()?; - let original_modification = get_modified_date(&self.temp_file)?; - open_temp_file_with_editor(&self.temp_file, opener)?; + // open_temp_file_with_editor(&self.temp_file, status)?; - watch_modification_in_thread(&self.temp_file, original_modification)?; + // self.watch_modification_in_thread(original_modification, fm_sender); + Ok(self) + } - let new_filenames = get_new_filenames(&self.temp_file)?; + fn watch_modification_in_thread(&self, fm_sender: Arc<Sender<FmEvents>>) -> Result<()> { + let original_modification = get_modified_date(&self.temp_file)?; + let filepath = self.temp_file.to_owned(); + thread::spawn(move || { + loop { + if is_file_modified(&filepath, original_modification).unwrap_or(true) { + break; + } + thread::sleep(Duration::from_millis(100)); + } + fm_sender.send(FmEvents::BulkExecute).unwrap_or_default(); + }); + Ok(()) + } - if new_filenames.len() < self.original_filepath.len() { - std::fs::remove_file(&self.temp_file)?; - return Err(anyhow!("new filenames: not enough filenames")); - } - self.new_filenames = new_filenames; - Ok(self) + fn get_new_names(&mut self) -> Result<()> { + self.new_filenames = get_new_filenames(&self.temp_file)?; + Ok(()) } fn write_original_names(&self) -> Result<()> { @@ -88,6 +77,18 @@ impl Renamer { Ok(()) } + fn execute(&self) -> Result<(OptionVecPathBuf, OptionVecPathBuf)> { + let paths = self.rename_create(); + self.del_temporary_file()?; + paths + } + + fn rename_create(&self) -> Result<(OptionVecPathBuf, OptionVecPathBuf)> { + let renamed_paths = self.rename_all(&self.new_filenames)?; + let created_paths = self.create_all_files(&self.new_filenames)?; + Ok((renamed_paths, created_paths)) + } + fn rename_all(&self, new_filenames: &[String]) -> Result<Option<Vec<PathBuf>>> { let mut paths = vec![]; for (path, filename) in self.original_filepath.iter().zip(new_filenames.iter()) { @@ -102,48 +103,10 @@ impl Renamer { log_line!("Bulk renamed {len} files", len = paths.len()); Ok(Some(paths)) } -} - -struct Creator { - parent_dir: String, - temp_file: PathBuf, - new_filenames: Vec<String>, -} - -impl DelTemp for Creator {} - -impl BulkExecute for Creator { - fn execute(&self) -> Result<Option<Vec<PathBuf>>> { - let paths = self.create_all_files(&self.new_filenames)?; - self.del_temporary_file(&self.temp_file)?; - Ok(paths) - } -} - -impl Creator { - fn new(path_str: &str) -> Self { - let temp_file = generate_random_filepath(); - Self { - parent_dir: path_str.to_owned(), - temp_file, - new_filenames: vec![], - } - } - - fn ask_filenames(mut self, opener: &Opener) -> Result<Self> { - create_random_file(&self.temp_file)?; - log_info!("created {temp_file}", temp_file = self.temp_file.display()); - let original_modification = get_modified_date(&self.temp_file)?; - open_temp_file_with_editor(&self.temp_file, opener)?; - - watch_modification_in_thread(&self.temp_file, original_modification)?; - self.new_filenames = get_new_filenames(&self.temp_file)?; - Ok(self) - } fn create_all_files(&self, new_filenames: &[String]) -> Result<Option<Vec<PathBuf>>> { let mut paths = vec![]; - for filename in new_filenames.iter() { + for filename in new_filenames.iter().skip(self.original_filepath.len()) { let Some(path) = self.create_file(filename)? else { continue; }; @@ -175,25 +138,10 @@ impl Creator { } Ok(Some(new_path)) } -} -fn generate_random_filepath() -> PathBuf { - let mut filepath = PathBuf::from(&TMP_FOLDER_PATH); - filepath.push(random_name()); - filepath -} - -fn watch_modification_in_thread(filepath: &Path, original_modification: SystemTime) -> Result<()> { - let filepath = filepath.to_owned(); - let handle = thread::spawn(move || loop { - if is_file_modified(&filepath, original_modification).unwrap_or(true) { - break; - } - thread::sleep(Duration::from_millis(100)); - }); - match handle.join() { - Ok(handle) => Ok(handle), - Err(e) => Err(anyhow!("watch thread failed {e:?}")), + fn del_temporary_file(&self) -> Result<()> { + std::fs::remove_file(&self.temp_file)?; + Ok(()) } } @@ -201,16 +149,17 @@ fn get_modified_date(filepath: &Path) -> Result<SystemTime> { Ok(std::fs::metadata(filepath)?.modified()?) } +fn generate_random_filepath() -> PathBuf { + let mut filepath = PathBuf::from(&TMP_FOLDER_PATH); + filepath.push(random_name()); + filepath +} + fn create_random_file(temp_file: &Path) -> Result<()> { std::fs::File::create(temp_file)?; Ok(()) } -fn open_temp_file_with_editor(temp_file: &Path, opener: &Opener) -> Result<()> { - log_info!("opening tempory file {:?}", temp_file); - opener.open_single(temp_file) -} - fn is_file_modified(path: &Path, original_modification: std::time::SystemTime) -> Result<bool> { Ok(get_modified_date(path)? > original_modification) } @@ -229,109 +178,104 @@ fn get_new_filenames(temp_file: &Path) -> Result<Vec<String>> { } pub struct Bulk { - pub content: Vec<String>, - index: usize, - renamer: Option<Renamer>, - creator: Option<Creator>, -} - -impl Default for Bulk { - fn default() -> Self { - Self { - content: vec![ - "Rename files".to_string(), - "New files or folders. End folder with a slash '/'".to_string(), - ], - index: 0, - renamer: None, - creator: None, - } - } + bulk: Option<BulkExecutor>, + fm_sender: Arc<Sender<FmEvents>>, } +/// Bulk holds a `BulkExecutor` only when bulk actionmap, `None` otherwise. +/// +/// Once `ask_filenames` is executed, a new tmp file is created. It's filled with every filename +/// of flagged files in current directory. +/// Modifications of this file are watched in a separate thread. +/// Once the file is written, its content is parsed and a confirmation is asked : `format_confirmation` +/// Renaming or creating is execute in bulk with `execute`. impl Bulk { - /// True if Bulk is set to rename files. - /// False if Bulk is set to create files. - pub fn is_rename(&self) -> bool { - self.index == 0 - } - - pub fn bulk_mode(&self) -> BulkAction { - if self.is_rename() { - BulkAction::Rename - } else { - BulkAction::Create + pub fn new(fm_sender: Arc<Sender<FmEvents>>) -> Self { + Self { + bulk: None, + fm_sender, } } - - /// Reset to default values. + /// Reset bulk content to None, droping all created or renomed filename from previous execution. pub fn reset(&mut self) { - self.renamer = None; - self.creator = None; - self.index = 0; + self.bulk = None; } - /// Ask the new filenames + /// Ask user for filename. + /// + /// Creates a temp file with every flagged filename in current dir. + /// Modification of this file are then watched in a thread. + /// The mode will change to BulkAction once something is written in the file. + /// + /// # Errors /// - /// Both may fail if the current user can't write in /tmp, since - /// they create a temporary file. + /// May fail if the user can't create a file, read or write in /tmp + /// May also fail if the watching thread fail. pub fn ask_filenames( &mut self, flagged_in_current_dir: Vec<PathBuf>, current_tab_path_str: &str, - opener: &Opener, - ) -> Result<BulkAction> { - if self.is_rename() { - self.renamer = Some(Renamer::new(flagged_in_current_dir).ask_filenames(opener)?); - } else { - self.creator = Some(Creator::new(current_tab_path_str).ask_filenames(opener)?); + ) -> Result<()> { + self.bulk = + Some(BulkExecutor::new(flagged_in_current_dir, current_tab_path_str).ask_filenames()?); + Ok(()) + } + + pub fn watch_in_thread(&mut self) -> Result<()> { + match &self.bulk { + Some(bulk) => bulk.watch_modification_in_thread(self.fm_sender.clone())?, + None => (), } - Ok(self.bulk_mode()) + Ok(()) } - /// Execute the selected bulk method depending on the index. - /// First method is a rename of selected files, - /// Second is the creation of files or folders, - /// - /// # Errors - /// - /// renamer may fail if we can't rename a file (permissions...) - /// creator may fail if we can't write in current directory. - pub fn execute(&mut self) -> Result<Option<Vec<PathBuf>>> { - log_info!("bulk execute: {action:?}", action = self.bulk_mode()); - let paths = if self.is_rename() { - let Some(renamer) = &mut self.renamer else { - return Ok(None); - }; - renamer.execute()? - } else { - let Some(creator) = &mut self.creator else { - return Ok(None); - }; - creator.execute()? - }; - self.reset(); - Ok(paths) + pub fn get_new_names(&mut self) -> Result<()> { + if let Some(bulk) = &mut self.bulk { + bulk.get_new_names()?; + } + Ok(()) } + /// String representation of the filetree modifications. pub fn format_confirmation(&self) -> Vec<String> { - if let Some(renamer) = &self.renamer { - renamer + if let Some(bulk) = &self.bulk { + let mut lines: Vec<String> = bulk .original_filepath .iter() - .zip(renamer.new_filenames.iter()) + .zip(bulk.new_filenames.iter()) .map(|(original, new)| { - format!("{original} -> {new}", original = original.display()) + format!("RENAME: {original} -> {new}", original = original.display()) }) - .collect() - } else if let Some(creator) = &self.creator { - creator.new_filenames.clone() + .collect(); + for new in bulk.new_filenames.iter().skip(bulk.original_filepath.len()) { + lines.push(format!("CREATE: {new}")); + } + lines } else { vec![] } } + + /// Execute the action parsed from the file. + /// + /// # Errors + /// + /// May fail if bulk is still set to None. It should never happen. + /// May fail if the new file can't be created or the flagged file can't be renamed. + pub fn execute(&mut self) -> Result<(OptionVecPathBuf, OptionVecPathBuf)> { + let Some(bulk) = &mut self.bulk else { + return Err(anyhow!("bulk shouldn't be None")); + }; + let ret = bulk.execute(); + self.reset(); + ret + } + + /// Optional temporary file where filenames are edited by the user + /// None if `self.bulk` is `None`. + pub fn temp_file(&self) -> Option<PathBuf> { + self.bulk.as_ref().map(|bulk| bulk.temp_file.to_owned()) + } } -// impl_selectable_content!(String, Bulk); -impl_selectable!(Bulk); -impl_content!(String, Bulk); +type OptionVecPathBuf = Option<Vec<PathBuf>>; |