diff --git a/src/commands/file_ops.rs b/src/commands/file_ops.rs index be0c0ec21..3575429d5 100644 --- a/src/commands/file_ops.rs +++ b/src/commands/file_ops.rs @@ -1,11 +1,26 @@ -use std::io; +use std::fs::{self, File}; +use std::io::{self, Read}; +use std::io::Write; +use std::path; use std::process::{Command, Stdio}; +use crate::config::{search_directories, ConfigType}; use crate::context::{AppContext, LocalStateContext}; use crate::error::{AppError, AppErrorKind, AppResult}; use crate::io::{FileOperation, FileOperationOptions, IoWorkerThread}; -fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option<()> { +use crate::{BOOKMARKS_T, CONFIG_HIERARCHY}; + +fn find_state_context_file() -> Option { + for p in CONFIG_HIERARCHY.iter() { + if p.exists() { + return Some(p.clone()); + } + } + None +} + +fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option { let list = context.tab_context_ref().curr_tab_ref().curr_list_ref()?; let selected = list.get_selected_paths(); @@ -13,27 +28,45 @@ fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option<( local_state.set_paths(selected.into_iter()); local_state.set_file_op(file_op); - context.set_local_state(local_state); - Some(()) + Some(local_state) } pub fn cut(context: &mut AppContext) -> AppResult { - new_local_state(context, FileOperation::Cut); + let local_state = new_local_state(context, FileOperation::Cut).unwrap_or(LocalStateContext::new()); + context.set_local_state(local_state); Ok(()) } pub fn copy(context: &mut AppContext) -> AppResult { - new_local_state(context, FileOperation::Copy); + let local_state = new_local_state(context, FileOperation::Copy).unwrap_or(LocalStateContext::new()); + context.set_local_state(local_state); Ok(()) } pub fn symlink_absolute(context: &mut AppContext) -> AppResult { - new_local_state(context, FileOperation::Symlink { relative: false }); + let local_state = new_local_state(context, FileOperation::Symlink { relative: false }).unwrap_or(LocalStateContext::new()); + context.set_local_state(local_state); Ok(()) } pub fn symlink_relative(context: &mut AppContext) -> AppResult { - new_local_state(context, FileOperation::Symlink { relative: true }); + let local_state = new_local_state(context, FileOperation::Symlink { relative: true }).unwrap_or(LocalStateContext::new()); + context.set_local_state(local_state); + Ok(()) +} + +pub fn save_local_state(local_state: &LocalStateContext) -> AppResult { + let local_state_path = match search_directories(ConfigType::StateContext.as_filename(), &CONFIG_HIERARCHY) { + Some(file_path) => Some(file_path), + None => find_state_context_file(), + }; + + if let Some(local_state_path) = local_state_path { + if let Ok(content) = toml::to_string(&local_state) { + let mut file = File::create(local_state_path)?; + file.write_all(content.as_bytes())?; + } + } Ok(()) } @@ -52,6 +85,48 @@ pub fn paste(context: &mut AppContext, options: FileOperationOptions) -> AppResu } } +pub fn cut_export(context: &mut AppContext) -> AppResult { + let local_state = new_local_state(context, FileOperation::Cut).unwrap_or(LocalStateContext::new()); + let _ = save_local_state(&local_state); + Ok(()) +} + +pub fn copy_export(context: &mut AppContext) -> AppResult { + let local_state = new_local_state(context, FileOperation::Copy).unwrap_or(LocalStateContext::new()); + let _ = save_local_state(&local_state); + Ok(()) +} +pub fn symlink_absolute_export(context: &mut AppContext) -> AppResult { + let local_state = new_local_state(context, FileOperation::Symlink { relative: false }).unwrap_or(LocalStateContext::new()); + let _ = save_local_state(&local_state); + Ok(()) +} + +pub fn symlink_relative_export(context: &mut AppContext) -> AppResult { + let local_state = new_local_state(context, FileOperation::Symlink { relative: true }).unwrap_or(LocalStateContext::new()); + let _ = save_local_state(&local_state); + Ok(()) +} + +pub fn paste_import(context: &mut AppContext, options: FileOperationOptions) -> AppResult { + + let local_state_path = match search_directories(ConfigType::StateContext.as_filename(), &CONFIG_HIERARCHY) { + Some(file_path) => Some(file_path), + None => find_state_context_file(), + }; + + if let Some(local_state_path) = local_state_path { + let file_contents = fs::read_to_string(&local_state_path)?; + let local_state = toml::from_str::(&file_contents).unwrap(); + + let dest = context.tab_context_ref().curr_tab_ref().cwd().to_path_buf(); + let worker_thread = IoWorkerThread::new(local_state.file_op, local_state.paths, dest, options); + context.worker_context_mut().push_worker(worker_thread); + } + Ok(()) +} + + pub fn copy_filename(context: &mut AppContext) -> AppResult { let entry_file_name = context .tab_context_ref() diff --git a/src/config/config_type.rs b/src/config/config_type.rs index 7a4755aa8..99f37d925 100644 --- a/src/config/config_type.rs +++ b/src/config/config_type.rs @@ -7,6 +7,7 @@ pub enum ConfigType { Preview, Bookmarks, Icons, + StateContext, } impl std::fmt::Display for ConfigType { @@ -35,6 +36,7 @@ impl ConfigType { Self::Preview, Self::Bookmarks, Self::Icons, + Self::StateContext, ] } @@ -47,6 +49,7 @@ impl ConfigType { Self::Preview => "preview", Self::Bookmarks => "bookmarks", Self::Icons => "icons", + Self::StateContext => "state_context", } } @@ -59,6 +62,7 @@ impl ConfigType { Self::Preview => "preview.toml", Self::Bookmarks => "bookmarks.toml", Self::Icons => "icons.toml", + Self::StateContext => "state_context.toml" } } @@ -69,7 +73,7 @@ impl ConfigType { Self::Keymap => Some(clean::keymap::DEFAULT_CONFIG_FILE_PATH), Self::Theme => Some(clean::theme::DEFAULT_CONFIG_FILE_PATH), Self::Icons => Some(clean::icon::DEFAULT_CONFIG_FILE_PATH), - Self::Mimetype | Self::Preview | Self::Bookmarks => None, + Self::Mimetype | Self::Preview | Self::Bookmarks | Self::StateContext => None, } } } diff --git a/src/context/local_state.rs b/src/context/local_state.rs index 049d90d19..6963856b6 100644 --- a/src/context/local_state.rs +++ b/src/context/local_state.rs @@ -1,8 +1,10 @@ use crate::io::FileOperation; +use serde::{Serialize, Deserialize}; use std::iter::Iterator; use std::path; +#[derive(Serialize, Deserialize)] pub struct LocalStateContext { pub paths: Vec, pub file_op: FileOperation, diff --git a/src/io/file_operation.rs b/src/io/file_operation.rs index c6dfb74d8..ef740e972 100644 --- a/src/io/file_operation.rs +++ b/src/io/file_operation.rs @@ -1,6 +1,7 @@ use std::path; +use serde::{Serialize, Deserialize}; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum FileOperation { Cut, Copy, diff --git a/src/key_command/command.rs b/src/key_command/command.rs index c0c575dab..71ce0411d 100644 --- a/src/key_command/command.rs +++ b/src/key_command/command.rs @@ -45,6 +45,15 @@ pub enum Command { options: FileOperationOptions, }, + CutFilesExport, + CopyFilesExport, + SymlinkFilesExport { + relative: bool, + }, + PasteFilesImport { + options: FileOperationOptions, + }, + DeleteFiles { background: bool, permanently: bool, diff --git a/src/key_command/constants.rs b/src/key_command/constants.rs index a7d4d9a6b..37e5acec9 100644 --- a/src/key_command/constants.rs +++ b/src/key_command/constants.rs @@ -33,6 +33,10 @@ cmd_constants![ (CMD_COPY_DIRECTORY_PATH, "copy_dirpath"), (CMD_SYMLINK_FILES, "symlink_files"), (CMD_PASTE_FILES, "paste_files"), + (CMD_CUT_FILES_EXPORT, "cut_files_export"), + (CMD_COPY_FILES_EXPORT, "copy_files_export"), + (CMD_SYMLINK_FILES_EXPORT, "symlink_files_export"), + (CMD_PASTE_FILES_IMPORT, "paste_files_import"), (CMD_DELETE_FILES, "delete_files"), (CMD_CURSOR_MOVE_UP, "cursor_move_up"), (CMD_CURSOR_MOVE_DOWN, "cursor_move_down"), diff --git a/src/key_command/impl_appcommand.rs b/src/key_command/impl_appcommand.rs index df9ffcfaf..b638c82f9 100644 --- a/src/key_command/impl_appcommand.rs +++ b/src/key_command/impl_appcommand.rs @@ -31,6 +31,11 @@ impl AppCommand for Command { Self::SymlinkFiles { .. } => CMD_SYMLINK_FILES, Self::PasteFiles { .. } => CMD_PASTE_FILES, + Self::CutFilesExport => CMD_CUT_FILES_EXPORT, + Self::CopyFilesExport => CMD_COPY_FILES_EXPORT, + Self::SymlinkFilesExport { .. } => CMD_SYMLINK_FILES_EXPORT, + Self::PasteFilesImport { .. } => CMD_PASTE_FILES_IMPORT, + Self::DeleteFiles { .. } => CMD_DELETE_FILES, Self::CursorMoveUp { .. } => CMD_CURSOR_MOVE_UP, diff --git a/src/key_command/impl_appexecute.rs b/src/key_command/impl_appexecute.rs index 3540cd14b..7394fcd92 100644 --- a/src/key_command/impl_appexecute.rs +++ b/src/key_command/impl_appexecute.rs @@ -50,6 +50,12 @@ impl AppExecute for Command { Self::SymlinkFiles { relative: false } => file_ops::symlink_absolute(context), Self::PasteFiles { options } => file_ops::paste(context, *options), + Self::CutFilesExport => file_ops::cut_export(context), + Self::CopyFilesExport => file_ops::copy_export(context), + Self::SymlinkFilesExport { relative: true } => file_ops::symlink_relative_export(context), + Self::SymlinkFilesExport { relative: false } => file_ops::symlink_absolute_export(context), + Self::PasteFilesImport { options } => file_ops::paste_import(context, *options), + Self::DeleteFiles { background, permanently, diff --git a/src/key_command/impl_comment.rs b/src/key_command/impl_comment.rs index 6fa744d01..6e7b661bf 100644 --- a/src/key_command/impl_comment.rs +++ b/src/key_command/impl_comment.rs @@ -40,14 +40,14 @@ impl CommandComment for Command { _ => "Open a command line", }, - Self::CutFiles => "Cut selected files", - Self::CopyFiles => "Copy selected files", + Self::CutFiles | Self::CutFilesExport => "Cut selected files", + Self::CopyFiles | Self::CopyFilesExport => "Copy selected files", Self::CopyFileName => "Copy filename", Self::CopyFileNameWithoutExtension => "Copy filename without extension", Self::CopyFilePath { all_selected: true } => "Copy all selected paths to file", Self::CopyFilePath { .. } => "Copy path to file", Self::CopyDirPath => "Copy directory name", - Self::SymlinkFiles { .. } => "Symlink selected files", + Self::SymlinkFiles { .. } | Self::SymlinkFilesExport { .. } => "Symlink selected files", Self::PasteFiles { options: @@ -56,6 +56,13 @@ impl CommandComment for Command { skip_exist, .. }, + } | Self::PasteFilesImport { + options: + FileOperationOptions { + overwrite, + skip_exist, + .. + }, } => match (overwrite, skip_exist) { (true, false) => "Paste, overwrite", (false, true) => "Paste, skip existing files", diff --git a/src/key_command/impl_from_str.rs b/src/key_command/impl_from_str.rs index d63407d67..1f958daee 100644 --- a/src/key_command/impl_from_str.rs +++ b/src/key_command/impl_from_str.rs @@ -74,6 +74,8 @@ impl std::str::FromStr for Command { simple_command_conversion_case!(command, CMD_CUT_FILES, Self::CutFiles); simple_command_conversion_case!(command, CMD_COPY_FILES, Self::CopyFiles); + simple_command_conversion_case!(command, CMD_CUT_FILES_EXPORT, Self::CutFilesExport); + simple_command_conversion_case!(command, CMD_COPY_FILES_EXPORT, Self::CopyFilesExport); simple_command_conversion_case!(command, CMD_COPY_FILENAME, Self::CopyFileName); simple_command_conversion_case!( command, @@ -230,6 +232,21 @@ impl std::str::FromStr for Command { } } Ok(Self::SymlinkFiles { relative }) + } else if command == CMD_SYMLINK_FILES_EXPORT { + let mut relative = false; + for arg in arg.split_whitespace() { + match arg { + "--relative=true" => relative = true, + "--relative=false" => relative = false, + _ => { + return Err(AppError::new( + AppErrorKind::UnrecognizedArgument, + format!("{}: unknown option '{}'", command, arg), + )); + } + } + } + Ok(Self::SymlinkFilesExport { relative }) } else if command == CMD_COPY_FILEPATH { let mut all_selected = false; for arg in arg.split_whitespace() { @@ -262,6 +279,23 @@ impl std::str::FromStr for Command { } } Ok(Self::PasteFiles { options }) + } else if command == CMD_PASTE_FILES_IMPORT { + let mut options = FileOperationOptions::default(); + for arg in arg.split_whitespace() { + match arg { + "--overwrite=true" => options.overwrite = true, + "--skip_exist=true" => options.skip_exist = true, + "--overwrite=false" => options.overwrite = false, + "--skip_exist=false" => options.skip_exist = false, + _ => { + return Err(AppError::new( + AppErrorKind::UnrecognizedArgument, + format!("{}: unknown option '{}'", command, arg), + )); + } + } + } + Ok(Self::PasteFilesImport { options }) } else if command == CMD_DELETE_FILES { let [mut permanently, mut background, mut noconfirm] = [false; 3]; for arg in arg.split_whitespace() {