diff options
Diffstat (limited to 'src/modes/edit/bulkrename.rs')
-rw-r--r-- | src/modes/edit/bulkrename.rs | 246 |
1 files changed, 173 insertions, 73 deletions
diff --git a/src/modes/edit/bulkrename.rs b/src/modes/edit/bulkrename.rs index dfba7d5..8f909cf 100644 --- a/src/modes/edit/bulkrename.rs +++ b/src/modes/edit/bulkrename.rs @@ -4,25 +4,49 @@ use std::path::{Path, PathBuf}; use std::thread; use std::time::{Duration, SystemTime}; -use crate::app::Status; -use crate::common::random_name; use crate::common::TMP_FOLDER_PATH; +use crate::common::{random_name, rename}; use crate::io::Opener; +use crate::log_info; use crate::log_line; -use crate::{impl_selectable_content, log_info}; +use crate::modes::mode::BulkAction; +use crate::{impl_content, impl_selectable}; -struct Renamer<'a> { - original_filepath: Vec<&'a Path>, +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 { + original_filepath: Vec<PathBuf>, temp_file: PathBuf, + new_filenames: Vec<String>, } -impl<'a> Renamer<'a> { +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<&'a Path>) -> Self { + fn new(original_filepath: Vec<PathBuf>) -> Self { let temp_file = generate_random_filepath(); Self { original_filepath, temp_file, + new_filenames: vec![], } } @@ -30,7 +54,7 @@ impl<'a> Renamer<'a> { /// The tempory file is opened with our `Opener` crate, allowing us /// to use the default text file editor. /// Filenames are sanitized before processing. - fn rename(&mut self, opener: &Opener) -> Result<()> { + fn ask_filenames(mut self, opener: &Opener) -> Result<Self> { self.write_original_names()?; let original_modification = get_modified_date(&self.temp_file)?; open_temp_file_with_editor(&self.temp_file, opener)?; @@ -43,10 +67,8 @@ impl<'a> Renamer<'a> { std::fs::remove_file(&self.temp_file)?; return Err(anyhow!("new filenames: not enough filenames")); } - - self.rename_all(new_filenames)?; - std::fs::remove_file(&self.temp_file)?; - Ok(()) + self.new_filenames = new_filenames; + Ok(self) } fn write_original_names(&self) -> Result<()> { @@ -66,80 +88,92 @@ impl<'a> Renamer<'a> { Ok(()) } - fn rename_all(&self, new_filenames: Vec<String>) -> Result<()> { - let mut counter = 0; + 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()) { - let new_name = sanitize_filename::sanitize(filename); - self.rename_file(path, &new_name)?; - counter += 1; - log_line!("Bulk renamed {path} to {new_name}", path = path.display()); + match rename(path, filename) { + Ok(path) => paths.push(path), + Err(error) => log_info!( + "Error renaming {path} to {filename}. Error: {error:?}", + path = path.display() + ), + } } - log_line!("Bulk renamed {counter} files"); - Ok(()) - } - - fn rename_file(&self, path: &Path, filename: &str) -> Result<()> { - let mut parent = PathBuf::from(path); - parent.pop(); - std::fs::rename(path, parent.join(filename))?; - Ok(()) + log_line!("Bulk renamed {len} files", len = paths.len()); + Ok(Some(paths)) } } -struct Creator<'a> { - parent_dir: &'a str, +struct Creator { + parent_dir: String, temp_file: PathBuf, + new_filenames: Vec<String>, } -impl<'a> Creator<'a> { - fn new(path_str: &'a str) -> Self { +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, + parent_dir: path_str.to_owned(), temp_file, + new_filenames: vec![], } } - fn create_files(&mut self, opener: &Opener) -> Result<()> { + 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.create_all_files(&get_new_filenames(&self.temp_file)?)?; - std::fs::remove_file(&self.temp_file)?; - Ok(()) + self.new_filenames = get_new_filenames(&self.temp_file)?; + Ok(self) } - fn create_all_files(&self, new_filenames: &[String]) -> Result<()> { - let mut counter = 0; + fn create_all_files(&self, new_filenames: &[String]) -> Result<Option<Vec<PathBuf>>> { + let mut paths = vec![]; for filename in new_filenames.iter() { - let mut new_path = std::path::PathBuf::from(self.parent_dir); - if !filename.ends_with('/') { - new_path.push(filename); - let Some(parent) = new_path.parent() else { - return Ok(()); - }; - log_info!("Bulk new files. Creating parent: {}", parent.display()); - if std::fs::create_dir_all(parent).is_err() { - continue; - }; - log_info!("creating: {new_path:?}"); - std::fs::File::create(&new_path)?; - log_line!("Bulk created {new_path}", new_path = new_path.display()); - counter += 1; - } else { - new_path.push(filename); - log_info!("Bulk creating dir: {}", new_path.display()); - std::fs::create_dir_all(&new_path)?; - log_line!("Bulk created {new_path}", new_path = new_path.display()); - counter += 1; - } + let Some(path) = self.create_file(filename)? else { + continue; + }; + paths.push(path) } - log_line!("Bulk created {counter} files"); - Ok(()) + log_line!("Bulk created {len} files", len = paths.len()); + Ok(Some(paths)) + } + + fn create_file(&self, filename: &str) -> Result<Option<PathBuf>> { + let mut new_path = std::path::PathBuf::from(&self.parent_dir); + if !filename.ends_with('/') { + new_path.push(filename); + let Some(parent) = new_path.parent() else { + return Ok(None); + }; + log_info!("Bulk new files. Creating parent: {}", parent.display()); + if std::fs::create_dir_all(parent).is_err() { + return Ok(None); + }; + log_info!("creating: {new_path:?}"); + std::fs::File::create(&new_path)?; + log_line!("Bulk created {new_path}", new_path = new_path.display()); + } else { + new_path.push(filename); + log_info!("Bulk creating dir: {}", new_path.display()); + std::fs::create_dir_all(&new_path)?; + log_line!("Bulk created {new_path}", new_path = new_path.display()); + } + Ok(Some(new_path)) } } @@ -197,6 +231,8 @@ 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 { @@ -207,11 +243,52 @@ impl Default for Bulk { "New files or folders. End folder with a slash '/'".to_string(), ], index: 0, + renamer: None, + creator: None, } } } 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 + } + } + + /// Reset to default values. + pub fn reset(&mut self) { + self.renamer = None; + self.creator = None; + self.index = 0; + } + + /// Ask the new filenames + /// + /// Both may fail if the current user can't write in /tmp, since + /// they create a temporary file. + 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)?); + } + Ok(self.bulk_mode()) + } + /// 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, @@ -220,18 +297,41 @@ impl Bulk { /// /// renamer may fail if we can't rename a file (permissions...) /// creator may fail if we can't write in current directory. - /// Both may fail if the current user can't write in /tmp, since - /// they create a temporary file. - pub fn execute_bulk(&self, status: &Status) -> Result<()> { - match self.index { - 0 => Renamer::new(status.flagged_in_current_dir()) - .rename(&status.internal_settings.opener)?, - 1 => Creator::new(&status.current_tab_path_str()) - .create_files(&status.internal_settings.opener)?, - _ => (), + 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()? }; - Ok(()) + self.reset(); + Ok(paths) + } + + pub fn format_confirmation(&self) -> Vec<String> { + if let Some(renamer) = &self.renamer { + renamer + .original_filepath + .iter() + .zip(renamer.new_filenames.iter()) + .map(|(original, new)| { + format!("{original} -> {new}", original = original.display()) + }) + .collect() + } else if let Some(creator) = &self.creator { + creator.new_filenames.clone() + } else { + vec![] + } } } -impl_selectable_content!(String, Bulk); +// impl_selectable_content!(String, Bulk); +impl_selectable!(Bulk); +impl_content!(String, Bulk); |