diff --git a/src/commands/cursor_move.rs b/src/commands/cursor_move.rs index e8dd168f3..e3fc79d4d 100644 --- a/src/commands/cursor_move.rs +++ b/src/commands/cursor_move.rs @@ -76,7 +76,7 @@ pub fn to_path(app_state: &mut AppState, path: &path::Path) -> AppResult { { if let path::Component::Normal(name) = path.components().next().ok_or_else(err)? { let index = curr_list.get_index_from_name(name.to_str().ok_or_else(err)?); - curr_list.set_index(index, &ui_state, &display_options); + curr_list.set_index(index, &ui_state, display_options); } } diff --git a/src/commands/delete_files.rs b/src/commands/delete_files.rs index b4838bf34..3c8aefbd2 100644 --- a/src/commands/delete_files.rs +++ b/src/commands/delete_files.rs @@ -4,10 +4,10 @@ use std::sync::mpsc; use termion::event::Key; use crate::error::{AppError, AppErrorKind, AppResult}; -use crate::io::{FileOperation, FileOperationOptions, IoWorkerThread}; use crate::types::state::AppState; use crate::ui::widgets::TuiPrompt; use crate::ui::AppBackend; +use crate::workers::io::{FileOperation, FileOperationOptions, IoWorkerThread}; use super::tab_ops; diff --git a/src/commands/file_ops.rs b/src/commands/file_ops.rs index 02c95666d..b4131e3a7 100644 --- a/src/commands/file_ops.rs +++ b/src/commands/file_ops.rs @@ -1,8 +1,8 @@ use std::process::{Command, Stdio}; use crate::error::{AppError, AppErrorKind, AppResult}; -use crate::io::{FileOperation, FileOperationOptions, IoWorkerThread}; use crate::types::state::{AppState, LocalStateState}; +use crate::workers::io::{FileOperation, FileOperationOptions, IoWorkerThread}; fn new_local_state(app_state: &mut AppState, file_op: FileOperation) -> Option<()> { let list = app_state diff --git a/src/commands/open_file.rs b/src/commands/open_file.rs index 3da50c98c..055bf5a45 100644 --- a/src/commands/open_file.rs +++ b/src/commands/open_file.rs @@ -62,10 +62,7 @@ where { if option.get_fork() { let (child_id, handle) = fork_execute(option, files, app_state.clone_event_tx())?; - app_state - .state - .worker_state_mut() - .push_child(child_id, handle); + app_state.state.thread_pool.push_child(child_id, handle); } else { backend.terminal_drop(); let res = execute_and_wait(option, files); @@ -246,10 +243,9 @@ pub fn open_with_interactive(app_state: &mut AppState, backend: &mut AppBackend) .curr_tab_ref() .curr_list_ref() .and_then(|s| s.curr_entry_ref()) - .map(|s| s.clone()) { Some(entry) => { - paths.push(entry); + paths.push(entry.clone()); } None => { let err = AppError::new(AppErrorKind::Io, "No files selected".to_string()); diff --git a/src/commands/tab_ops.rs b/src/commands/tab_ops.rs index 21d3ab272..7aa915ea9 100644 --- a/src/commands/tab_ops.rs +++ b/src/commands/tab_ops.rs @@ -8,8 +8,7 @@ use crate::error::{AppError, AppErrorKind, AppResult}; use crate::history::{ create_dirlist_with_history, generate_entries_to_root, DirectoryHistory, JoshutoHistory, }; -use crate::tab::{JoshutoTab, TabHomePage}; -use crate::types::option::tab::NewTabMode; +use crate::tab::{JoshutoTab, NewTabMode, TabHomePage}; use crate::types::state::AppState; use crate::utils::{cwd, unix}; diff --git a/src/config/app.rs b/src/config/app.rs index 7b367bf85..fd7822cba 100644 --- a/src/config/app.rs +++ b/src/config/app.rs @@ -67,7 +67,7 @@ impl From for AppConfig { zoxide_update: raw.zoxide_update, display_options: DisplayOption::from(raw.display_options), preview_options: PreviewOption::from(raw.preview_options), - search_options: SearchOption::from(raw.search_options), + search_options: raw.search_options, tab_options: TabOption::from(raw.tab_options), custom_commands: raw.custom_commands, } diff --git a/src/config/preview/mod.rs b/src/config/preview/mod.rs index 6fb9e3435..d1fda4d7e 100644 --- a/src/config/preview/mod.rs +++ b/src/config/preview/mod.rs @@ -1,3 +1,38 @@ -pub mod preview; pub mod preview_option_raw; -pub mod preview_raw; + +use std::collections::HashMap; + +use crate::{ + error::{AppError, AppErrorKind, AppResult}, + preview::preview_entry::FileEntryPreviewEntry, + traits::config::search_config_directories, + types::config_type::ConfigType, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct FileEntryPreview { + #[serde(default)] + pub extension: HashMap, + #[serde(default)] + pub mimetype: HashMap, +} + +impl FileEntryPreview { + pub fn from_toml_str(s: &str) -> AppResult { + let res = toml::from_str(s)?; + Ok(res) + } + + pub fn get_config() -> AppResult { + let file_path = search_config_directories(ConfigType::Preview.as_filename()) + .ok_or_else(|| AppError::new(AppErrorKind::Config, "Cannot find config".to_string()))?; + let file_contents = std::fs::read_to_string(file_path)?; + Self::from_toml_str(&file_contents) + } + + pub fn get_config_or_default() -> Self { + Self::get_config().unwrap_or_default() + } +} diff --git a/src/config/preview/preview.rs b/src/config/preview/preview.rs deleted file mode 100644 index 6987e62b8..000000000 --- a/src/config/preview/preview.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::collections::HashMap; - -use serde::Deserialize; - -use crate::{traits::config::TomlConfigFile, types::config_type::ConfigType}; - -use super::preview_raw::FileEntryPreviewRaw; - -#[derive(Debug, Deserialize)] -pub struct FileEntryPreviewEntry { - pub program: String, - pub args: Option>, -} - -#[derive(Debug, Default)] -pub struct FileEntryPreview { - pub extension: HashMap, - pub mimetype: HashMap, -} - -impl TomlConfigFile for FileEntryPreview { - type Raw = FileEntryPreviewRaw; - - fn get_type() -> ConfigType { - ConfigType::Preview - } -} - -impl From for FileEntryPreview { - fn from(raw: FileEntryPreviewRaw) -> Self { - let extension = raw.extension.unwrap_or_default(); - let mimetype = raw.mimetype.unwrap_or_default(); - - Self { - extension, - mimetype, - } - } -} diff --git a/src/config/preview/preview_option_raw.rs b/src/config/preview/preview_option_raw.rs index c7c8b633f..bb08d8ade 100644 --- a/src/config/preview/preview_option_raw.rs +++ b/src/config/preview/preview_option_raw.rs @@ -4,13 +4,7 @@ use toml::Value; use crate::types::option::preview::{PreviewProtocol, XDGThumbSizes}; -pub const fn default_max_preview_size() -> u64 { - 2 * 1024 * 1024 // 2 MB -} - -pub const fn default_true() -> bool { - true -} +use crate::utils::serde::{default_max_preview_size, default_true}; #[derive(Clone, Debug, Deserialize)] pub struct PreviewOptionRaw { diff --git a/src/config/preview/preview_raw.rs b/src/config/preview/preview_raw.rs deleted file mode 100644 index 201fafb67..000000000 --- a/src/config/preview/preview_raw.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::collections::HashMap; - -use serde::Deserialize; - -use super::preview::FileEntryPreviewEntry; - -#[derive(Debug, Default, Deserialize)] -pub struct FileEntryPreviewRaw { - pub extension: Option>, - pub mimetype: Option>, -} diff --git a/src/config/sort_option_raw.rs b/src/config/sort_option_raw.rs index a800b9730..6a92880d4 100644 --- a/src/config/sort_option_raw.rs +++ b/src/config/sort_option_raw.rs @@ -1,6 +1,6 @@ use serde::Deserialize; -use super::preview::preview_option_raw::default_true; +use crate::utils::serde::default_true; #[derive(Clone, Debug, Deserialize)] pub struct SortOptionRaw { diff --git a/src/config/theme/mod.rs b/src/config/theme/mod.rs index 00938dcea..d62f0704a 100644 --- a/src/config/theme/mod.rs +++ b/src/config/theme/mod.rs @@ -2,5 +2,98 @@ pub mod style; pub mod style_raw; pub mod tab; pub mod tab_raw; -pub mod theme; pub mod theme_raw; + +use std::collections::HashMap; + +use lscolors::LsColors; + +use crate::constants::config::THEME_CONFIG; +use crate::error::AppResult; +use crate::traits::config::TomlConfigFile; +use crate::types::config_type::ConfigType; + +use style::AppStyle; +use tab::TabTheme; +use theme_raw::AppThemeRaw; + +#[derive(Clone, Debug)] +pub struct AppTheme { + pub tabs: TabTheme, + pub regular: AppStyle, + pub selection: AppStyle, + pub visual_mode_selection: AppStyle, + pub directory: AppStyle, + pub executable: AppStyle, + pub link: AppStyle, + pub link_invalid: AppStyle, + pub socket: AppStyle, + pub ext: HashMap, + pub lscolors: Option, +} + +impl AppTheme { + pub fn default_res() -> AppResult { + let raw: AppThemeRaw = toml::from_str(THEME_CONFIG)?; + Ok(Self::from(raw)) + } +} + +impl TomlConfigFile for AppTheme { + type Raw = AppThemeRaw; + + fn get_type() -> ConfigType { + ConfigType::Theme + } +} + +impl std::default::Default for AppTheme { + fn default() -> Self { + // This should not fail. + // If it fails then there is a (syntax) error in the default config file + Self::default_res().unwrap() + } +} + +impl From for AppTheme { + fn from(raw: AppThemeRaw) -> Self { + let tabs = raw.tabs; + let selection = raw.selection.to_style_theme(); + let visual_mode_selection = raw.visual_mode_selection.to_style_theme(); + let executable = raw.executable.to_style_theme(); + let regular = raw.regular.to_style_theme(); + let directory = raw.directory.to_style_theme(); + let link = raw.link.to_style_theme(); + let link_invalid = raw.link_invalid.to_style_theme(); + let socket = raw.socket.to_style_theme(); + let ext: HashMap = raw + .ext + .iter() + .map(|(k, v)| { + let style = v.to_style_theme(); + (k.clone(), style) + }) + .collect(); + let lscolors = if raw.lscolors_enabled { + let lscolors = LsColors::from_env(); + let default = Some(LsColors::default()); + lscolors.or(default) + } else { + None + }; + + Self { + selection, + visual_mode_selection, + executable, + regular, + directory, + link, + link_invalid, + socket, + ext, + tabs: TabTheme::from(tabs), + lscolors, + } + } +} diff --git a/src/config/theme/theme.rs b/src/config/theme/theme.rs deleted file mode 100644 index dd4f4cd4a..000000000 --- a/src/config/theme/theme.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::collections::HashMap; - -use lscolors::LsColors; - -use crate::constants::config::THEME_CONFIG; -use crate::error::AppResult; -use crate::traits::config::TomlConfigFile; -use crate::types::config_type::ConfigType; - -use super::style::AppStyle; -use super::tab::TabTheme; -use super::theme_raw::AppThemeRaw; - -#[derive(Clone, Debug)] -pub struct AppTheme { - pub tabs: TabTheme, - pub regular: AppStyle, - pub selection: AppStyle, - pub visual_mode_selection: AppStyle, - pub directory: AppStyle, - pub executable: AppStyle, - pub link: AppStyle, - pub link_invalid: AppStyle, - pub socket: AppStyle, - pub ext: HashMap, - pub lscolors: Option, -} - -impl AppTheme { - pub fn default_res() -> AppResult { - let raw: AppThemeRaw = toml::from_str(THEME_CONFIG)?; - Ok(Self::from(raw)) - } -} - -impl TomlConfigFile for AppTheme { - type Raw = AppThemeRaw; - - fn get_type() -> ConfigType { - ConfigType::Theme - } -} - -impl std::default::Default for AppTheme { - fn default() -> Self { - // This should not fail. - // If it fails then there is a (syntax) error in the default config file - Self::default_res().unwrap() - } -} - -impl From for AppTheme { - fn from(raw: AppThemeRaw) -> Self { - let tabs = raw.tabs; - let selection = raw.selection.to_style_theme(); - let visual_mode_selection = raw.visual_mode_selection.to_style_theme(); - let executable = raw.executable.to_style_theme(); - let regular = raw.regular.to_style_theme(); - let directory = raw.directory.to_style_theme(); - let link = raw.link.to_style_theme(); - let link_invalid = raw.link_invalid.to_style_theme(); - let socket = raw.socket.to_style_theme(); - let ext: HashMap = raw - .ext - .iter() - .map(|(k, v)| { - let style = v.to_style_theme(); - (k.clone(), style) - }) - .collect(); - let lscolors = if raw.lscolors_enabled { - let lscolors = LsColors::from_env(); - let default = Some(LsColors::default()); - lscolors.or(default) - } else { - None - }; - - Self { - selection, - visual_mode_selection, - executable, - regular, - directory, - link, - link_invalid, - socket, - ext, - tabs: TabTheme::from(tabs), - lscolors, - } - } -} diff --git a/src/fs/dirlist.rs b/src/fs/dirlist.rs index 53fa91315..47bde2de7 100644 --- a/src/fs/dirlist.rs +++ b/src/fs/dirlist.rs @@ -1,24 +1,24 @@ use std::slice::{Iter, IterMut}; use std::{io, path}; -use crate::fs::{JoshutoDirEntry, JoshutoMetadata}; +use crate::fs::{entry::JoshutoDirEntry, metadata::JoshutoMetadata}; use crate::history::read_directory; +use crate::tab::TabDisplayOption; use crate::types::option::display::DisplayOption; -use crate::types::option::tab::TabDisplayOption; use crate::types::state::UiState; #[derive(Clone, Debug)] pub struct JoshutoDirList { - path: path::PathBuf, + pub path: path::PathBuf, pub contents: Vec, pub metadata: JoshutoMetadata, /// The cursor position in this dir list - index: Option, + pub index: Option, /// The index in this dir list to start with when rendering the list - viewport_index: usize, + pub viewport_index: usize, /// The index in this dir list where visual mode has started or None if not in visual mode - visual_mode_anchor_index: Option, - _need_update: bool, + pub visual_mode_anchor_index: Option, + pub need_update: bool, } impl JoshutoDirList { @@ -37,24 +37,20 @@ impl JoshutoDirList { index, viewport_index, visual_mode_anchor_index, - _need_update: false, + need_update: false, } } pub fn from_path( path: path::PathBuf, display_options: &DisplayOption, - tab_display_options: &TabDisplayOption, + tab_options: &TabDisplayOption, ) -> io::Result { let filter_func = display_options.filter_func(); - let mut contents = read_directory( - path.as_path(), - filter_func, - display_options, - tab_display_options, - )?; + let mut contents = + read_directory(path.as_path(), filter_func, display_options, tab_options)?; - contents.sort_by(|f1, f2| tab_display_options.sort_options_ref().compare(f1, f2)); + contents.sort_by(|f1, f2| tab_options.sort_options.compare(f1, f2)); let index = if contents.is_empty() { None } else { Some(0) }; let metadata = JoshutoMetadata::from(&path)?; @@ -63,7 +59,7 @@ impl JoshutoDirList { path, contents, metadata, - _need_update: false, + need_update: false, index, viewport_index: index.unwrap_or_default(), visual_mode_anchor_index: None, @@ -80,7 +76,6 @@ impl JoshutoDirList { return Some(index); } } - None } @@ -207,11 +202,11 @@ impl JoshutoDirList { } pub fn depreciate(&mut self) { - self._need_update = true; + self.need_update = true; } pub fn need_update(&self) -> bool { - self._need_update || self.modified() + self.need_update || self.modified() } pub fn file_path(&self) -> &path::Path { diff --git a/src/fs/entry.rs b/src/fs/entry.rs index be112f741..dce7d56e9 100644 --- a/src/fs/entry.rs +++ b/src/fs/entry.rs @@ -1,6 +1,6 @@ use std::{fs, io, path}; -use crate::{fs::JoshutoMetadata, types::option::display::DisplayOption}; +use crate::{fs::metadata::JoshutoMetadata, types::option::display::DisplayOption}; #[derive(Clone, Debug)] pub struct JoshutoDirEntry { diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 159d63b9b..7fd292902 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,7 +1,9 @@ mod dirlist; mod entry; mod metadata; +mod options; -pub use self::dirlist::JoshutoDirList; -pub use self::entry::JoshutoDirEntry; -pub use self::metadata::{FileType, JoshutoMetadata, LinkType}; +pub use dirlist::*; +pub use entry::*; +pub use metadata::*; +pub use options::*; diff --git a/src/types/option/fs/display.rs b/src/fs/options.rs similarity index 66% rename from src/types/option/fs/display.rs rename to src/fs/options.rs index 5d150a6fb..156d035f8 100644 --- a/src/types/option/fs/display.rs +++ b/src/fs/options.rs @@ -1,10 +1,10 @@ use crate::types::state::MatchState; /// Display options valid pre JoshutoDirList in a JoshutoTab -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct DirListDisplayOptions { - filter_state: MatchState, - depth: u8, + pub filter_state: MatchState, + pub depth: u8, } impl DirListDisplayOptions { @@ -24,3 +24,12 @@ impl DirListDisplayOptions { self.depth } } + +impl std::default::Default for DirListDisplayOptions { + fn default() -> Self { + Self { + filter_state: MatchState::None, + depth: 0, + } + } +} diff --git a/src/history.rs b/src/history.rs index c3aaa4778..251ba5899 100644 --- a/src/history.rs +++ b/src/history.rs @@ -4,10 +4,9 @@ use std::path::{Path, PathBuf}; use walkdir::WalkDir; -use crate::fs::{JoshutoDirEntry, JoshutoDirList, JoshutoMetadata}; +use crate::fs::{DirListDisplayOptions, JoshutoDirEntry, JoshutoDirList, JoshutoMetadata}; +use crate::tab::TabDisplayOption; use crate::types::option::display::DisplayOption; -use crate::types::option::fs::DirListDisplayOptions; -use crate::types::option::tab::TabDisplayOption; use crate::types::state::UiState; pub trait DirectoryHistory { diff --git a/src/io/io_observer.rs b/src/io/io_observer.rs deleted file mode 100644 index 7508aa29d..000000000 --- a/src/io/io_observer.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::path; -use std::thread; - -use crate::io::FileOperationProgress; -use crate::utils::format; - -#[derive(Debug)] -pub struct IoWorkerObserver { - pub handle: thread::JoinHandle<()>, - pub progress: Option, - msg: String, - src: path::PathBuf, - dest: path::PathBuf, -} - -impl IoWorkerObserver { - pub fn new(handle: thread::JoinHandle<()>, src: path::PathBuf, dest: path::PathBuf) -> Self { - Self { - handle, - progress: None, - src, - dest, - msg: String::new(), - } - } - - pub fn join(self) -> bool { - self.handle.join().is_ok() - } - pub fn set_progress(&mut self, progress: FileOperationProgress) { - self.progress = Some(progress); - } - pub fn update_msg(&mut self) { - match self.progress.as_ref() { - None => {} - Some(progress) => { - let op_str = progress.kind().actioning_str(); - let processed_size = format::file_size_to_string(progress.bytes_processed()); - let total_size = format::file_size_to_string(progress.total_bytes()); - - let msg = format!( - "{} ({}/{}) ({}/{}) completed", - op_str, - progress.files_processed() + 1, - progress.total_files(), - processed_size, - total_size, - ); - self.msg = msg; - } - } - } - pub fn get_msg(&self) -> &str { - self.msg.as_str() - } - pub fn src_path(&self) -> &path::Path { - self.src.as_path() - } - pub fn dest_path(&self) -> &path::Path { - self.dest.as_path() - } -} diff --git a/src/main.rs b/src/main.rs index 9a6274055..65003698e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ mod constants; mod error; mod fs; mod history; -mod io; mod preview; mod run; mod shadow; @@ -13,6 +12,7 @@ mod traits; mod types; mod ui; mod utils; +mod workers; use std::fs::File; use std::io::prelude::*; @@ -25,8 +25,8 @@ use config::app::AppConfig; use config::bookmarks::Bookmarks; use config::icon::AppIcons; use config::mimetype::AppProgramRegistry; -use config::preview::preview::FileEntryPreview; -use config::theme::theme::AppTheme; +use config::preview::FileEntryPreview; +use config::theme::AppTheme; use lazy_static::lazy_static; use traits::config::TomlConfigFile; @@ -70,7 +70,7 @@ lazy_static! { }; static ref THEME_T: AppTheme = AppTheme::get_config(); static ref MIMETYPE_T: AppProgramRegistry = AppProgramRegistry::get_config(); - static ref PREVIEW_T: FileEntryPreview = FileEntryPreview::get_config(); + static ref PREVIEW_T: FileEntryPreview = FileEntryPreview::get_config_or_default(); static ref BOOKMARKS_T: Mutex = Mutex::new(Bookmarks::get_config()); static ref ICONS_T: AppIcons = AppIcons::get_config(); diff --git a/src/preview/mod.rs b/src/preview/mod.rs index ebab23f7b..bc011be31 100644 --- a/src/preview/mod.rs +++ b/src/preview/mod.rs @@ -1,3 +1,4 @@ pub mod preview_default; pub mod preview_dir; +pub mod preview_entry; pub mod preview_file; diff --git a/src/preview/preview_default.rs b/src/preview/preview_default.rs index df2134c3c..2f1c76603 100644 --- a/src/preview/preview_default.rs +++ b/src/preview/preview_default.rs @@ -5,6 +5,29 @@ use crate::preview::preview_dir; use crate::types::state::AppState; use crate::ui::AppBackend; +pub fn load_previews(app_state: &mut AppState, backend: &mut AppBackend) { + let mut load_list = Vec::with_capacity(2); + + let curr_tab = app_state.state.tab_state_ref().curr_tab_ref(); + match curr_tab.curr_list_ref() { + Some(curr_list) => { + if let Some(index) = curr_list.get_index() { + let entry = &curr_list.contents[index]; + load_list.push((entry.file_path().to_path_buf(), entry.metadata.clone())); + } + } + None => { + if let Ok(metadata) = JoshutoMetadata::from(curr_tab.get_cwd()) { + load_list.push((curr_tab.get_cwd().to_path_buf(), metadata)); + } + } + } + + for (path, metadata) in load_list { + load_preview_path(app_state, backend, path, metadata); + } +} + pub fn load_preview_path( app_state: &mut AppState, backend: &mut AppBackend, @@ -31,29 +54,9 @@ pub fn load_preview_path( preview_dir::Background::load_preview(app_state, p); } } else if metadata.len() <= preview_options.max_preview_size { - app_state.state.load_preview(&app_state.config, backend, p); - } -} - -pub fn load_preview(app_state: &mut AppState, backend: &mut AppBackend) { - let mut load_list = Vec::with_capacity(2); - - let curr_tab = app_state.state.tab_state_ref().curr_tab_ref(); - match curr_tab.curr_list_ref() { - Some(curr_list) => { - if let Some(index) = curr_list.get_index() { - let entry = &curr_list.contents[index]; - load_list.push((entry.file_path().to_path_buf(), entry.metadata.clone())); - } - } - None => { - if let Ok(metadata) = JoshutoMetadata::from(curr_tab.get_cwd()) { - load_list.push((curr_tab.get_cwd().to_path_buf(), metadata)); - } - } - } - - for (path, metadata) in load_list { - load_preview_path(app_state, backend, path, metadata); + app_state + .state + .preview_state_mut() + .load_preview(&app_state.config, backend, p); } } diff --git a/src/preview/preview_dir.rs b/src/preview/preview_dir.rs index ef89cea76..8eb51233a 100644 --- a/src/preview/preview_dir.rs +++ b/src/preview/preview_dir.rs @@ -20,7 +20,10 @@ impl PreviewDirState { pub struct Background {} impl Background { - pub fn load_preview(app_state: &mut AppState, p: path::PathBuf) -> thread::JoinHandle<()> { + pub fn load_preview( + app_state: &mut AppState, + dir_path: path::PathBuf, + ) -> thread::JoinHandle<()> { let event_tx = app_state.events.event_tx.clone(); let options = app_state.config.display_options.clone(); let tab_options = app_state @@ -37,11 +40,11 @@ impl Background { .tab_state_mut() .curr_tab_mut() .history_metadata_mut() - .insert(p.clone(), PreviewDirState::Loading); + .insert(dir_path.clone(), PreviewDirState::Loading); thread::spawn(move || { - let path_clone = p.clone(); - let dir_res = JoshutoDirList::from_path(p, &options, &tab_options); + let path_clone = dir_path.clone(); + let dir_res = JoshutoDirList::from_path(dir_path, &options, &tab_options); let res = AppEvent::PreviewDir { id: tab_id, path: path_clone, diff --git a/src/preview/preview_entry.rs b/src/preview/preview_entry.rs new file mode 100644 index 000000000..58185eeeb --- /dev/null +++ b/src/preview/preview_entry.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct FileEntryPreviewEntry { + pub program: String, + pub args: Option>, +} diff --git a/src/preview/preview_file.rs b/src/preview/preview_file.rs index ab734287f..4a7f9b5ff 100644 --- a/src/preview/preview_file.rs +++ b/src/preview/preview_file.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use std::{process::Output, time}; +#[derive(Clone)] pub enum PreviewFileState { Loading, Error(String), diff --git a/src/run/mod.rs b/src/run/mod.rs index d490986db..8a24b509f 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -57,7 +57,7 @@ pub fn run_loop( app_state.state.tab_state_mut().insert_tab(id, tab, true); // trigger a preview of child - preview_default::load_preview(app_state, backend); + preview_default::load_previews(app_state, backend); } while app_state.quit == QuitAction::DoNot { @@ -112,7 +112,7 @@ fn process_input( match event { AppEvent::Termion(Event::Mouse(event)) => { process_event::process_mouse(app_state, backend, keymap_t, event); - preview_default::load_preview(app_state, backend); + preview_default::load_previews(app_state, backend); } AppEvent::Termion(key) => { if app_state @@ -165,7 +165,7 @@ fn process_input( } }, } - preview_default::load_preview(app_state, backend); + preview_default::load_previews(app_state, backend); app_state.flush_event(); } event => process_event::process_noninteractive(event, app_state), diff --git a/src/run/process_event.rs b/src/run/process_event.rs index 4f9e6d506..0843f6b33 100644 --- a/src/run/process_event.rs +++ b/src/run/process_event.rs @@ -11,7 +11,6 @@ use crate::commands::tab_ops; use crate::commands::{cursor_move, parent_cursor_move, reload}; use crate::error::AppResult; use crate::fs::JoshutoDirList; -use crate::io::FileOperationProgress; use crate::preview::preview_dir::PreviewDirState; use crate::preview::preview_file::PreviewFileState; use crate::traits::app_execute::AppExecute; @@ -25,6 +24,7 @@ use crate::types::state::AppState; use crate::ui; use crate::ui::views::TuiCommandMenu; use crate::utils::format; +use crate::workers::io::IoWorkerProgressMessage; pub fn poll_event_until_simple_keybind<'a>( app_state: &mut AppState, @@ -71,7 +71,7 @@ pub fn process_noninteractive(event: AppEvent, app_state: &mut AppState) { AppEvent::Signal(signal::SIGWINCH) => {} AppEvent::Filesystem(e) => process_filesystem_event(e, app_state), AppEvent::ChildProcessComplete(child_id) => { - app_state.state.worker_state_mut().join_child(child_id); + app_state.state.thread_pool.join_child(child_id); } _ => {} } @@ -85,17 +85,19 @@ pub fn process_new_worker(app_state: &mut AppState) { if !app_state.state.worker_state_ref().is_busy() && !app_state.state.worker_state_ref().is_empty() { - app_state.state.worker_state_mut().start_next_job(); + let _ = app_state.state.worker_state_mut().start_next_job(); } } -pub fn process_worker_progress(app_state: &mut AppState, res: FileOperationProgress) { +pub fn process_worker_progress(app_state: &mut AppState, res: IoWorkerProgressMessage) { let worker_state = app_state.state.worker_state_mut(); - worker_state.set_progress(res); - worker_state.update_msg(); + if let Some(observer) = worker_state.observer.as_mut() { + observer.process_msg(res); + observer.update_msg(); + } } -pub fn process_finished_worker(app_state: &mut AppState, res: AppResult) { +pub fn process_finished_worker(app_state: &mut AppState, res: AppResult) { let worker_state = app_state.state.worker_state_mut(); let observer = worker_state.remove_worker().unwrap(); @@ -113,18 +115,17 @@ pub fn process_finished_worker(app_state: &mut AppState, res: AppResult { - let op = progress.kind().actioned_str(); - let processed_size = format::file_size_to_string(progress.bytes_processed()); - let total_size = format::file_size_to_string(progress.total_bytes()); + Ok(_) => { + let op = progress.kind.actioned_str(); + let processed_size = format::file_size_to_string(progress.bytes_processed); + let total_size = format::file_size_to_string(progress.total_bytes); let msg = format!( "successfully {} {} items ({}/{})", - op, - progress.total_files(), - processed_size, - total_size, + op, progress.total_files, processed_size, total_size, ); app_state.state.message_queue_mut().push_success(msg); } @@ -137,7 +138,7 @@ pub fn process_finished_worker(app_state: &mut AppState, res: AppResult; + +pub struct JoshutoTab { + pub cwd: path::PathBuf, + // history is just a HashMap, so we have this property to store last workdir + pub previous_dir: Option, + pub history: JoshutoHistory, + pub history_metadata: HistoryMetadata, + pub options: TabDisplayOption, +} + +impl JoshutoTab { + pub fn new( + cwd: path::PathBuf, + history: JoshutoHistory, + tab_options: TabDisplayOption, + ) -> std::io::Result { + let new_tab = Self { + cwd, + previous_dir: None, + history, + history_metadata: HashMap::new(), + options: tab_options, + }; + + Ok(new_tab) + } + + pub fn option_ref(&self) -> &TabDisplayOption { + &self.options + } + + pub fn option_mut(&mut self) -> &mut TabDisplayOption { + &mut self.options + } + + pub fn get_cwd(&self) -> &path::Path { + self.cwd.as_path() + } + pub fn set_cwd(&mut self, cwd: &path::Path) { + self.previous_dir = Some(self.cwd.to_path_buf()); + self.cwd = cwd.to_path_buf(); + + // OSC 7: Escape sequence to set the working directory + // print!("\x1b]7;file://{}{}\x1b\\", HOSTNAME.as_str(), cwd.display()); + } + + pub fn previous_dir(&self) -> Option<&path::Path> { + self.previous_dir.as_deref() + } + + pub fn history_ref(&self) -> &JoshutoHistory { + &self.history + } + pub fn history_mut(&mut self) -> &mut JoshutoHistory { + &mut self.history + } + + pub fn history_metadata_ref(&self) -> &HistoryMetadata { + &self.history_metadata + } + pub fn history_metadata_mut(&mut self) -> &mut HistoryMetadata { + &mut self.history_metadata + } + + pub fn curr_list_ref(&self) -> Option<&JoshutoDirList> { + self.history.get(self.get_cwd()) + } + pub fn parent_list_ref(&self) -> Option<&JoshutoDirList> { + let parent = self.get_cwd().parent()?; + self.history.get(parent) + } + pub fn child_list_ref(&self) -> Option<&JoshutoDirList> { + let curr_list = self.curr_list_ref()?; + let index = curr_list.get_index()?; + let path = curr_list.contents[index].file_path(); + self.history.get(path) + } + + pub fn curr_list_mut(&mut self) -> Option<&mut JoshutoDirList> { + self.history.get_mut(self.cwd.as_path()) + } + pub fn parent_list_mut(&mut self) -> Option<&mut JoshutoDirList> { + let parent = self.cwd.parent()?; + self.history.get_mut(parent) + } + #[allow(dead_code)] + pub fn child_list_mut(&mut self) -> Option<&mut JoshutoDirList> { + let child_path = { + let curr_list = self.curr_list_ref()?; + let index = curr_list.get_index()?; + curr_list.contents[index].file_path().to_path_buf() + }; + + self.history.get_mut(child_path.as_path()) + } +} diff --git a/src/types/option/tab/new_tab_mode.rs b/src/tab/new_tab_mode.rs similarity index 100% rename from src/types/option/tab/new_tab_mode.rs rename to src/tab/new_tab_mode.rs diff --git a/src/types/option/tab/display.rs b/src/tab/options.rs similarity index 95% rename from src/types/option/tab/display.rs rename to src/tab/options.rs index 0efed6fb9..9b4f38100 100644 --- a/src/types/option/tab/display.rs +++ b/src/tab/options.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, path::PathBuf}; -use crate::types::option::fs::DirListDisplayOptions; +use crate::fs::DirListDisplayOptions; use crate::types::option::line_mode::LineMode; use crate::types::option::sort::SortOption; diff --git a/src/tab/tab_struct.rs b/src/tab/tab_struct.rs deleted file mode 100644 index cd38b7167..000000000 --- a/src/tab/tab_struct.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::collections::HashMap; -use std::path; - -use crate::fs::JoshutoDirList; -use crate::history::JoshutoHistory; -use crate::preview::preview_dir::PreviewDirState; -use crate::types::option::tab::TabDisplayOption; -// use crate::HOSTNAME; - -type HistoryMetadata = HashMap; - -pub struct JoshutoTab { - pub cwd: path::PathBuf, - // history is just a HashMap, so we have this property to store last workdir - pub previous_dir: Option, - pub history: JoshutoHistory, - pub history_metadata: HistoryMetadata, - pub options: TabDisplayOption, -} - -impl JoshutoTab { - pub fn new( - cwd: path::PathBuf, - history: JoshutoHistory, - tab_options: TabDisplayOption, - ) -> std::io::Result { - let new_tab = Self { - cwd, - previous_dir: None, - history, - history_metadata: HashMap::new(), - options: tab_options, - }; - - Ok(new_tab) - } - - pub fn option_ref(&self) -> &TabDisplayOption { - &self.options - } - - pub fn option_mut(&mut self) -> &mut TabDisplayOption { - &mut self.options - } - - pub fn get_cwd(&self) -> &path::Path { - self.cwd.as_path() - } - pub fn set_cwd(&mut self, cwd: &path::Path) { - self.previous_dir = Some(self.cwd.to_path_buf()); - self.cwd = cwd.to_path_buf(); - - // OSC 7: Escape sequence to set the working directory - // print!("\x1b]7;file://{}{}\x1b\\", HOSTNAME.as_str(), cwd.display()); - } - - pub fn previous_dir(&self) -> Option<&path::Path> { - self.previous_dir.as_ref().map(|p| p.as_path()) - } - - pub fn history_ref(&self) -> &JoshutoHistory { - &self.history - } - pub fn history_mut(&mut self) -> &mut JoshutoHistory { - &mut self.history - } - - pub fn history_metadata_ref(&self) -> &HistoryMetadata { - &self.history_metadata - } - pub fn history_metadata_mut(&mut self) -> &mut HistoryMetadata { - &mut self.history_metadata - } - - pub fn curr_list_ref(&self) -> Option<&JoshutoDirList> { - self.history.get(self.get_cwd()) - } - pub fn parent_list_ref(&self) -> Option<&JoshutoDirList> { - let parent = self.get_cwd().parent()?; - self.history.get(parent) - } - pub fn child_list_ref(&self) -> Option<&JoshutoDirList> { - let curr_list = self.curr_list_ref()?; - let index = curr_list.get_index()?; - let path = curr_list.contents[index].file_path(); - self.history.get(path) - } - - pub fn curr_list_mut(&mut self) -> Option<&mut JoshutoDirList> { - self.history.get_mut(self.cwd.as_path()) - } - pub fn parent_list_mut(&mut self) -> Option<&mut JoshutoDirList> { - let parent = self.cwd.parent()?; - self.history.get_mut(parent) - } - #[allow(dead_code)] - pub fn child_list_mut(&mut self) -> Option<&mut JoshutoDirList> { - let child_path = { - let curr_list = self.curr_list_ref()?; - let index = curr_list.get_index()?; - curr_list.contents[index].file_path().to_path_buf() - }; - - self.history.get_mut(child_path.as_path()) - } -} diff --git a/src/types/command/command.rs b/src/types/command/command.rs deleted file mode 100644 index 1afcde2e8..000000000 --- a/src/types/command/command.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::path; - -use crate::commands::case_sensitivity::SetType; -use crate::commands::quit::QuitAction; -use crate::commands::select::SelectOption; -use crate::commands::stdout::PostProcessor; -use crate::commands::sub_process::SubprocessCallMode; -use crate::io::FileOperationOptions; -use crate::types::option::line_mode::{LineMode, LineNumberStyle}; -use crate::types::option::search::CaseSensitivity; -use crate::types::option::sort::SortMethod; -use crate::types::option::tab::NewTabMode; - -#[derive(Clone, Debug)] -#[allow(clippy::enum_variant_names)] -pub enum Command { - Escape, - ToggleVisualMode, - BulkRename, - - ChangeDirectory { - path: path::PathBuf, - }, - ParentDirectory, - PreviousDirectory, - - CommandLine { - prefix: String, - suffix: String, - }, - - CutFiles, - CopyFiles, - CopyFileName, - CopyFileNameWithoutExtension, - CopyFilePath { - all_selected: bool, - }, - CopyDirPath, - SymlinkFiles { - relative: bool, - }, - PasteFiles { - options: FileOperationOptions, - }, - - DeleteFiles { - background: bool, - permanently: bool, - noconfirm: bool, - }, - - CursorMoveUp { - offset: usize, - }, - CursorMoveDown { - offset: usize, - }, - CursorMoveHome, - CursorMoveEnd, - CursorMovePageUp(f64), - CursorMovePageDown(f64), - CursorMovePageHome, - CursorMovePageMiddle, - CursorMovePageEnd, - - SetLineMode(LineMode), - - ParentCursorMoveUp { - offset: usize, - }, - ParentCursorMoveDown { - offset: usize, - }, - - PreviewCursorMoveUp { - offset: usize, - }, - PreviewCursorMoveDown { - offset: usize, - }, - - // ChildCursorMoveUp(usize), - // ChildCursorMoveDown(usize), - NewDirectory { - path: path::PathBuf, - }, - OpenFile, - OpenFileWith { - index: Option, - }, - Quit(QuitAction), - - ReloadDirList, - RenameFile { - new_name: path::PathBuf, - }, - RenameFileAppend, - RenameFileAppendBase, - RenameFilePrepend, - RenameFileKeepExt, - TouchFile { - file_name: String, - }, - - SearchGlob { - pattern: String, - }, - SearchRegex { - pattern: String, - }, - SearchString { - pattern: String, - }, - SearchIncremental { - pattern: String, - }, - SearchNext, - SearchPrev, - - SelectGlob { - pattern: String, - options: SelectOption, - }, - SelectRegex { - pattern: String, - options: SelectOption, - }, - SelectString { - pattern: String, - options: SelectOption, - }, - - SetCaseSensitivity { - case_sensitivity: CaseSensitivity, - set_type: SetType, - }, - SetMode, - SubProcess { - words: Vec, - mode: SubprocessCallMode, - }, - ShowTasks, - - ToggleHiddenFiles, - SwitchLineNums(LineNumberStyle), - - Flat { - depth: usize, - }, - NumberedCommand { - initial: char, - }, - - Sort { - sort_method: SortMethod, - reverse: Option, - }, - SortReverse, - - FilterGlob { - pattern: String, - }, - FilterRegex { - pattern: String, - }, - FilterString { - pattern: String, - }, - - NewTab { - mode: NewTabMode, - last: bool, - }, - CloseTab, - TabSwitch { - offset: i32, - }, - TabSwitchIndex { - index: usize, - }, - Help, - - SearchFzf, - SubdirFzf, - SelectFzf { - options: SelectOption, - }, - StdOutPostProcess { - processor: PostProcessor, - }, - Zoxide(String), - ZoxideInteractive(String), - - CustomSearch(Vec), - CustomSearchInteractive(Vec), - - BookmarkAdd, - BookmarkChangeDirectory, -} diff --git a/src/types/command/impl_comment.rs b/src/types/command/impl_comment.rs index 755252e73..0d6a9cd12 100644 --- a/src/types/command/impl_comment.rs +++ b/src/types/command/impl_comment.rs @@ -1,7 +1,7 @@ use crate::commands::sub_process::SubprocessCallMode; -use crate::io::FileOperationOptions; use crate::traits::app_execute::CommandComment; use crate::types::option::sort::SortMethod; +use crate::workers::io::FileOperationOptions; use super::Command; diff --git a/src/types/command/impl_from_str.rs b/src/types/command/impl_from_str.rs index 971389d44..50e4cbfac 100644 --- a/src/types/command/impl_from_str.rs +++ b/src/types/command/impl_from_str.rs @@ -6,12 +6,12 @@ use crate::commands::select::SelectOption; use crate::commands::stdout::PostProcessor; use crate::commands::sub_process::SubprocessCallMode; use crate::error::{AppError, AppErrorKind}; -use crate::io::FileOperationOptions; +use crate::tab::NewTabMode; use crate::types::option::line_mode::{LineMode, LineNumberStyle}; use crate::types::option::search::CaseSensitivity; use crate::types::option::sort::SortMethod; -use crate::types::option::tab::NewTabMode; use crate::utils::unix; +use crate::workers::io::FileOperationOptions; use crate::HOME_DIR; diff --git a/src/types/command/mod.rs b/src/types/command/mod.rs index e0cd16018..baeef3695 100644 --- a/src/types/command/mod.rs +++ b/src/types/command/mod.rs @@ -1,4 +1,3 @@ -mod command; mod impl_appcommand; mod impl_appexecute; mod impl_comment; @@ -8,4 +7,203 @@ mod impl_from_str; mod impl_interactive; mod impl_numbered; -pub use command::*; +use std::path; + +use crate::commands::case_sensitivity::SetType; +use crate::commands::quit::QuitAction; +use crate::commands::select::SelectOption; +use crate::commands::stdout::PostProcessor; +use crate::commands::sub_process::SubprocessCallMode; +use crate::tab::NewTabMode; +use crate::types::option::line_mode::{LineMode, LineNumberStyle}; +use crate::types::option::search::CaseSensitivity; +use crate::types::option::sort::SortMethod; +use crate::workers::io::FileOperationOptions; + +#[derive(Clone, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum Command { + Escape, + ToggleVisualMode, + BulkRename, + + ChangeDirectory { + path: path::PathBuf, + }, + ParentDirectory, + PreviousDirectory, + + CommandLine { + prefix: String, + suffix: String, + }, + + CutFiles, + CopyFiles, + CopyFileName, + CopyFileNameWithoutExtension, + CopyFilePath { + all_selected: bool, + }, + CopyDirPath, + SymlinkFiles { + relative: bool, + }, + PasteFiles { + options: FileOperationOptions, + }, + + DeleteFiles { + background: bool, + permanently: bool, + noconfirm: bool, + }, + + CursorMoveUp { + offset: usize, + }, + CursorMoveDown { + offset: usize, + }, + CursorMoveHome, + CursorMoveEnd, + CursorMovePageUp(f64), + CursorMovePageDown(f64), + CursorMovePageHome, + CursorMovePageMiddle, + CursorMovePageEnd, + + SetLineMode(LineMode), + + ParentCursorMoveUp { + offset: usize, + }, + ParentCursorMoveDown { + offset: usize, + }, + + PreviewCursorMoveUp { + offset: usize, + }, + PreviewCursorMoveDown { + offset: usize, + }, + + // ChildCursorMoveUp(usize), + // ChildCursorMoveDown(usize), + NewDirectory { + path: path::PathBuf, + }, + OpenFile, + OpenFileWith { + index: Option, + }, + Quit(QuitAction), + + ReloadDirList, + RenameFile { + new_name: path::PathBuf, + }, + RenameFileAppend, + RenameFileAppendBase, + RenameFilePrepend, + RenameFileKeepExt, + TouchFile { + file_name: String, + }, + + SearchGlob { + pattern: String, + }, + SearchRegex { + pattern: String, + }, + SearchString { + pattern: String, + }, + SearchIncremental { + pattern: String, + }, + SearchNext, + SearchPrev, + + SelectGlob { + pattern: String, + options: SelectOption, + }, + SelectRegex { + pattern: String, + options: SelectOption, + }, + SelectString { + pattern: String, + options: SelectOption, + }, + + SetCaseSensitivity { + case_sensitivity: CaseSensitivity, + set_type: SetType, + }, + SetMode, + SubProcess { + words: Vec, + mode: SubprocessCallMode, + }, + ShowTasks, + + ToggleHiddenFiles, + SwitchLineNums(LineNumberStyle), + + Flat { + depth: usize, + }, + NumberedCommand { + initial: char, + }, + + Sort { + sort_method: SortMethod, + reverse: Option, + }, + SortReverse, + + FilterGlob { + pattern: String, + }, + FilterRegex { + pattern: String, + }, + FilterString { + pattern: String, + }, + + NewTab { + mode: NewTabMode, + last: bool, + }, + CloseTab, + TabSwitch { + offset: i32, + }, + TabSwitchIndex { + index: usize, + }, + Help, + + SearchFzf, + SubdirFzf, + SelectFzf { + options: SelectOption, + }, + StdOutPostProcess { + processor: PostProcessor, + }, + Zoxide(String), + ZoxideInteractive(String), + + CustomSearch(Vec), + CustomSearchInteractive(Vec), + + BookmarkAdd, + BookmarkChangeDirectory, +} diff --git a/src/types/config_type.rs b/src/types/config_type.rs index c2f8be48d..ae5b1919f 100644 --- a/src/types/config_type.rs +++ b/src/types/config_type.rs @@ -65,10 +65,10 @@ impl ConfigType { pub const fn embedded_config(&self) -> Option<&'static str> { use crate::constants::config::*; match self { - Self::App => Some(&APP_CONFIG), - Self::Keymap => Some(&KEYMAP_CONFIG), - Self::Theme => Some(&THEME_CONFIG), - Self::Icons => Some(&ICON_CONFIG), + Self::App => Some(APP_CONFIG), + Self::Keymap => Some(KEYMAP_CONFIG), + Self::Theme => Some(THEME_CONFIG), + Self::Icons => Some(ICON_CONFIG), Self::Mimetype | Self::Preview | Self::Bookmarks => None, } } diff --git a/src/types/dirlist/display_option.rs b/src/types/dirlist/display_option.rs deleted file mode 100644 index fcd27144b..000000000 --- a/src/types/dirlist/display_option.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::app_state::MatchContext; - -/// Display options valid pre JoshutoDirList in a JoshutoTab -#[derive(Clone, Debug, Default)] -pub struct DirListDisplayOptions { - filter_app_state: MatchContext, - depth: u8, -} - -impl DirListDisplayOptions { - pub fn set_filter_app_state(&mut self, filter_app_state: MatchContext) { - self.filter_app_state = filter_app_state; - } - - pub fn filter_app_state_ref(&self) -> &MatchContext { - &self.filter_app_state - } - - pub fn set_depth(&mut self, depth: u8) { - self.depth = depth; - } - - pub fn depth(&self) -> u8 { - self.depth - } -} \ No newline at end of file diff --git a/src/types/dirlist/mod.rs b/src/types/dirlist/mod.rs deleted file mode 100644 index 2fd36211d..000000000 --- a/src/types/dirlist/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod options; \ No newline at end of file diff --git a/src/types/event/app_event.rs b/src/types/event/app_event.rs index f6d538bce..1ee308af8 100644 --- a/src/types/event/app_event.rs +++ b/src/types/event/app_event.rs @@ -16,8 +16,8 @@ use uuid::Uuid; use crate::error::AppResult; use crate::fs::JoshutoDirList; -use crate::io::FileOperationProgress; use crate::preview::preview_file::FilePreview; +use crate::workers::io::IoWorkerProgressMessage; pub enum PreviewData { Script(Box), @@ -40,8 +40,8 @@ pub enum AppEvent { // background IO worker events IoWorkerCreate, - FileOperationProgress(FileOperationProgress), - IoWorkerResult(AppResult), + FileOperationProgress(IoWorkerProgressMessage), + IoWorkerResult(AppResult), // forked process events ChildProcessComplete(u32), diff --git a/src/types/option/display.rs b/src/types/option/display.rs index 213133478..c1553fe7f 100644 --- a/src/types/option/display.rs +++ b/src/types/option/display.rs @@ -2,9 +2,11 @@ use std::convert::From; use ratatui::layout::Constraint; -use crate::config::display_raw::DisplayOptionRaw; +use crate::{ + config::display_raw::DisplayOptionRaw, fs::DirListDisplayOptions, tab::TabDisplayOption, +}; -use super::{fs::DirListDisplayOptions, line_mode::LineNumberStyle, tab::TabDisplayOption}; +use super::line_mode::LineNumberStyle; #[derive(Clone, Copy, Debug)] pub enum DisplayMode { @@ -60,7 +62,7 @@ impl From for DisplayOption { ]; Self { - mode: mode, + mode, automatically_count_files: raw.automatically_count_files, collapse_preview: raw.collapse_preview, scroll_offset: raw.scroll_offset, diff --git a/src/types/option/fs/mod.rs b/src/types/option/fs/mod.rs deleted file mode 100644 index a59aa524a..000000000 --- a/src/types/option/fs/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod display; - -pub use display::*; diff --git a/src/types/option/line_mode/args.rs b/src/types/option/line_mode/args.rs new file mode 100644 index 000000000..1fd1a4b4e --- /dev/null +++ b/src/types/option/line_mode/args.rs @@ -0,0 +1,27 @@ +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +pub enum LineModeArgs { + Size, + ModifyTime, + AccessTime, + BirthTime, + User, + Group, + Permission, + #[default] + Null, +} + +impl AsRef for LineModeArgs { + fn as_ref(&self) -> &str { + match self { + LineModeArgs::Size => "size", + LineModeArgs::ModifyTime => "mtime", + LineModeArgs::AccessTime => "atime", + LineModeArgs::BirthTime => "ctime", + LineModeArgs::User => "user", + LineModeArgs::Group => "group", + LineModeArgs::Permission => "perm", + LineModeArgs::Null => unreachable!(), + } + } +} diff --git a/src/types/option/line_mode/line_mode.rs b/src/types/option/line_mode/line_mode.rs deleted file mode 100644 index ed2c81bc4..000000000 --- a/src/types/option/line_mode/line_mode.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::error::{AppError, AppErrorKind, AppResult}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LineMode { - pub mode: [LineModeArgs; 8], - pub size: usize, -} - -impl LineMode { - pub const fn all() -> Self { - Self { - mode: [ - LineModeArgs::Size, - LineModeArgs::ModifyTime, - LineModeArgs::AccessTime, - LineModeArgs::BirthTime, - LineModeArgs::User, - LineModeArgs::Group, - LineModeArgs::Permission, - LineModeArgs::Null, - ], - size: 7, - } - } - - pub const fn empty() -> Self { - Self { - mode: [LineModeArgs::Null; 8], - size: 0, - } - } - - pub fn add_mode(&mut self, mode: LineModeArgs) { - if self.mode.contains(&mode) { - return; - } - - self.mode[self.size] = mode; - self.size += 1; - } -} - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum LineModeArgs { - Size, - ModifyTime, - AccessTime, - BirthTime, - User, - Group, - Permission, - #[default] - Null, -} - -impl AsRef for LineModeArgs { - fn as_ref(&self) -> &str { - match self { - LineModeArgs::Size => "size", - LineModeArgs::ModifyTime => "mtime", - LineModeArgs::AccessTime => "atime", - LineModeArgs::BirthTime => "ctime", - LineModeArgs::User => "user", - LineModeArgs::Group => "group", - LineModeArgs::Permission => "perm", - LineModeArgs::Null => unreachable!(), - } - } -} - -impl Default for LineMode { - fn default() -> Self { - let mut mode = [Default::default(); 8]; - mode[0] = LineModeArgs::Size; - - Self { size: 1, mode } - } -} - -impl LineMode { - pub fn from_string(name: &str) -> AppResult { - match name { - "all" => Ok(LineMode::all()), - "none" => Ok(LineMode::empty()), - _ => { - let mut line_mode = LineMode::empty(); - - for mode in name.split('|').map(|mode| mode.trim()) { - match mode { - "size" => line_mode.add_mode(LineModeArgs::Size), - "mtime" => line_mode.add_mode(LineModeArgs::ModifyTime), - "atime" => line_mode.add_mode(LineModeArgs::AccessTime), - "btime" => line_mode.add_mode(LineModeArgs::BirthTime), - "user" => line_mode.add_mode(LineModeArgs::User), - "group" => line_mode.add_mode(LineModeArgs::Group), - "perm" => line_mode.add_mode(LineModeArgs::Permission), - e => { - return Err(AppError::new( - AppErrorKind::InvalidParameters, - format!("Linemode '{}' unknown.", e), - )) - } - } - } - - Ok(line_mode) - } - } - } - - pub fn as_string(&self) -> String { - let modes: Vec<&str> = self - .mode - .iter() - .take(self.size) - .map(AsRef::as_ref) - .collect(); - - modes.join(" | ") - } -} diff --git a/src/types/option/line_mode/mod.rs b/src/types/option/line_mode/mod.rs index 214ffa7a7..871f2d0d9 100644 --- a/src/types/option/line_mode/mod.rs +++ b/src/types/option/line_mode/mod.rs @@ -1,5 +1,99 @@ -mod line_mode; +mod args; mod line_number; -pub use line_mode::*; +pub use args::*; pub use line_number::*; + +use crate::error::{AppError, AppErrorKind, AppResult}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct LineMode { + pub mode: [LineModeArgs; 8], + pub size: usize, +} + +impl LineMode { + pub const fn all() -> Self { + Self { + mode: [ + LineModeArgs::Size, + LineModeArgs::ModifyTime, + LineModeArgs::AccessTime, + LineModeArgs::BirthTime, + LineModeArgs::User, + LineModeArgs::Group, + LineModeArgs::Permission, + LineModeArgs::Null, + ], + size: 7, + } + } + + pub const fn empty() -> Self { + Self { + mode: [LineModeArgs::Null; 8], + size: 0, + } + } + + pub fn add_mode(&mut self, mode: LineModeArgs) { + if self.mode.contains(&mode) { + return; + } + + self.mode[self.size] = mode; + self.size += 1; + } +} + +impl Default for LineMode { + fn default() -> Self { + let mut mode = [Default::default(); 8]; + mode[0] = LineModeArgs::Size; + + Self { size: 1, mode } + } +} + +impl LineMode { + pub fn from_string(name: &str) -> AppResult { + match name { + "all" => Ok(LineMode::all()), + "none" => Ok(LineMode::empty()), + _ => { + let mut line_mode = LineMode::empty(); + + for mode in name.split('|').map(|mode| mode.trim()) { + match mode { + "size" => line_mode.add_mode(LineModeArgs::Size), + "mtime" => line_mode.add_mode(LineModeArgs::ModifyTime), + "atime" => line_mode.add_mode(LineModeArgs::AccessTime), + "btime" => line_mode.add_mode(LineModeArgs::BirthTime), + "user" => line_mode.add_mode(LineModeArgs::User), + "group" => line_mode.add_mode(LineModeArgs::Group), + "perm" => line_mode.add_mode(LineModeArgs::Permission), + e => { + return Err(AppError::new( + AppErrorKind::InvalidParameters, + format!("Linemode '{}' unknown.", e), + )) + } + } + } + + Ok(line_mode) + } + } + } + + pub fn as_string(&self) -> String { + let modes: Vec<&str> = self + .mode + .iter() + .take(self.size) + .map(AsRef::as_ref) + .collect(); + + modes.join(" | ") + } +} diff --git a/src/types/option/mod.rs b/src/types/option/mod.rs index 9af162ef5..e46ca1820 100644 --- a/src/types/option/mod.rs +++ b/src/types/option/mod.rs @@ -1,7 +1,5 @@ pub mod display; -pub mod fs; pub mod line_mode; pub mod preview; pub mod search; pub mod sort; -pub mod tab; diff --git a/src/types/option/sort/mod.rs b/src/types/option/sort/mod.rs index 975e7f66e..b224a1ac1 100644 --- a/src/types/option/sort/mod.rs +++ b/src/types/option/sort/mod.rs @@ -1,5 +1,5 @@ -mod sort; mod sort_method; +mod sort_option; -pub use sort::*; pub use sort_method::*; +pub use sort_option::*; diff --git a/src/types/option/sort/sort.rs b/src/types/option/sort/sort_option.rs similarity index 100% rename from src/types/option/sort/sort.rs rename to src/types/option/sort/sort_option.rs diff --git a/src/types/option/tab/mod.rs b/src/types/option/tab/mod.rs deleted file mode 100644 index 698ba589c..000000000 --- a/src/types/option/tab/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod display; -mod new_tab_mode; - -pub use display::*; -pub use new_tab_mode::*; diff --git a/src/types/state/app_state.rs b/src/types/state/app_state.rs index c9301d9c0..a60dc3401 100644 --- a/src/types/state/app_state.rs +++ b/src/types/state/app_state.rs @@ -14,7 +14,7 @@ use crate::types::state::{ use crate::Args; -use super::FileManagerState; +use super::{FileManagerState, ThreadPool}; pub struct AppState { pub config: AppConfig, @@ -49,8 +49,7 @@ impl AppState { let events = Events::new(); let event_tx = events.event_tx.clone(); - let mut commandline_state = CommandLineState::new(); - let _ = commandline_state.history_mut().set_max_len(20); + let commandline_state = CommandLineState::new(); let event_tx_for_fs_notification = event_tx.clone(); let watcher = notify::recommended_watcher(move |res| { @@ -80,6 +79,7 @@ impl AppState { search_state: None, message_queue: MessageQueue::new(), worker_state: WorkerState::new(event_tx.clone()), + thread_pool: ThreadPool::new(), preview_state: PreviewState::new( picker, preview_script, diff --git a/src/types/state/commandline_state.rs b/src/types/state/commandline_state.rs index 8c26570a5..ba8f4a438 100644 --- a/src/types/state/commandline_state.rs +++ b/src/types/state/commandline_state.rs @@ -6,9 +6,9 @@ pub struct CommandLineState { impl std::default::Default for CommandLineState { fn default() -> Self { - Self { - history: MemHistory::new(), - } + let mut history = MemHistory::new(); + let _ = history.set_max_len(20); + Self { history } } } diff --git a/src/types/state/file_manager_state.rs b/src/types/state/file_manager_state.rs index 56fb0b362..0f58e8dd6 100644 --- a/src/types/state/file_manager_state.rs +++ b/src/types/state/file_manager_state.rs @@ -3,13 +3,12 @@ use std::path; use notify::{RecursiveMode, Watcher}; -use crate::config::app::AppConfig; -use crate::preview::preview_file::PreviewFileState; use crate::types::state::{ CommandLineState, LocalStateState, MatchState, MessageQueue, PreviewState, TabState, UiState, WorkerState, }; -use crate::ui::AppBackend; + +use super::ThreadPool; pub struct FileManagerState { // app_state related to tabs @@ -22,6 +21,8 @@ pub struct FileManagerState { pub message_queue: MessageQueue, // app_state related to io workers pub worker_state: WorkerState, + // thread pool of child processes + pub thread_pool: ThreadPool, // app_state related to previews pub preview_state: PreviewState, // app_state related to command line @@ -128,18 +129,4 @@ impl FileManagerState { pub fn commandline_state_mut(&mut self) -> &mut CommandLineState { &mut self.commandline_state } - pub fn load_preview(&mut self, config: &AppConfig, backend: &AppBackend, path: path::PathBuf) { - // always load image without cache - self.preview_state_mut().set_image_preview(None); - self.preview_state - .load_preview_image(config, backend, path.clone()); - - let previews = self.preview_state_mut().previews_mut(); - if previews.get(path.as_path()).is_none() { - // add to loading state - previews.insert(path.clone(), PreviewFileState::Loading); - self.preview_state - .load_preview_script(config, backend, path); - } - } } diff --git a/src/types/state/local_state.rs b/src/types/state/local_state.rs index 7c0c1676b..c68fabe3b 100644 --- a/src/types/state/local_state.rs +++ b/src/types/state/local_state.rs @@ -1,8 +1,9 @@ use std::iter::Iterator; use std::path; -use crate::io::FileOperation; +use crate::workers::io::FileOperation; +#[derive(Clone, Debug)] pub struct LocalStateState { pub paths: Vec, pub file_op: FileOperation, diff --git a/src/types/state/mod.rs b/src/types/state/mod.rs index 19069b670..a5b803894 100644 --- a/src/types/state/mod.rs +++ b/src/types/state/mod.rs @@ -6,6 +6,7 @@ mod matcher; mod message_queue; mod preview_state; mod tab_state; +mod thread_pool; mod ui_state; mod worker_state; @@ -17,5 +18,6 @@ pub use matcher::*; pub use message_queue::*; pub use preview_state::*; pub use tab_state::*; +pub use thread_pool::*; pub use ui_state::*; pub use worker_state::*; diff --git a/src/types/state/preview_state.rs b/src/types/state/preview_state.rs index 2734334ac..43fd0e1b5 100644 --- a/src/types/state/preview_state.rs +++ b/src/types/state/preview_state.rs @@ -38,7 +38,7 @@ pub struct PreviewState { pub sender_script: Sender<(PathBuf, Rect)>, pub sender_image: Option>, // for telling main thread when previews are ready - pub event_ts: Sender, + pub event_tx: Sender, } impl PreviewState { @@ -47,18 +47,18 @@ impl PreviewState { script: Option, allmytoes: Option, xdg_thumb_size: ThumbSize, - event_ts: Sender, + event_tx: Sender, ) -> PreviewState { let (sender_script, receiver) = mpsc::channel::<(PathBuf, Rect)>(); - let thread_script_event_ts = event_ts.clone(); + let thread_script_event_tx = event_tx.clone(); thread::spawn(move || { - for (path, rect) in receiver { - if let Some(ref script) = script { + if let Some(ref script) = script { + for (path, rect) in receiver { PreviewState::spawn_command( path.clone(), script.to_path_buf(), rect, - thread_script_event_ts.clone(), + thread_script_event_tx.clone(), ); } } @@ -66,7 +66,7 @@ impl PreviewState { let (sender_image, receiver) = mpsc::channel::<(PathBuf, Rect)>(); let sender_image = picker.map(|mut picker| { - let thread_image_event_ts = event_ts.clone(); + let thread_image_event_tx = event_tx.clone(); thread::spawn(move || loop { // Get last, or block for next. if let Some((path, rect)) = receiver @@ -98,7 +98,7 @@ impl PreviewState { path, res: Ok(PreviewData::Image(proto)), }; - let _ = thread_image_event_ts.send(ev); + let _ = thread_image_event_tx.send(ev); } } else { // Closed. @@ -114,7 +114,20 @@ impl PreviewState { image_preview: None, sender_script, sender_image, - event_ts, + event_tx, + } + } + + pub fn load_preview(&mut self, config: &AppConfig, backend: &AppBackend, path: path::PathBuf) { + // always load image without cache + self.set_image_preview(None); + self.load_preview_image(config, backend, path.clone()); + + let previews = self.previews_mut(); + if previews.get(path.as_path()).is_none() { + // add to loading state + previews.insert(path.clone(), PreviewFileState::Loading); + self.load_preview_script(config, backend, path); } } @@ -122,7 +135,7 @@ impl PreviewState { path: PathBuf, script: PathBuf, rect: Rect, - thread_event_ts: Sender, + thread_event_tx: Sender, ) { let output = Command::new(script) .stdout(Stdio::piped()) @@ -155,7 +168,7 @@ impl PreviewState { res: Err(io::Error::new(io::ErrorKind::Other, format!("{err}"))), }, }; - let _ = thread_event_ts.send(res); + let _ = thread_event_tx.send(res); } pub fn previews_ref(&self) -> &FilePreviewMetadata { @@ -189,7 +202,7 @@ impl PreviewState { path, res: Err(err), }; - let _ = self.event_ts.send(ev); + let _ = self.event_tx.send(ev); } } @@ -207,7 +220,7 @@ impl PreviewState { path, res: Err(err), }; - let _ = self.event_ts.send(ev); + let _ = self.event_tx.send(ev); } } } diff --git a/src/types/state/thread_pool.rs b/src/types/state/thread_pool.rs new file mode 100644 index 000000000..4ae5d2862 --- /dev/null +++ b/src/types/state/thread_pool.rs @@ -0,0 +1,25 @@ +use std::collections::HashMap; +use std::thread; + +#[derive(Debug)] +pub struct ThreadPool { + // forks of applications + pub child_pool: HashMap>, +} + +impl ThreadPool { + pub fn new() -> Self { + Self { + child_pool: HashMap::new(), + } + } + pub fn push_child(&mut self, child_id: u32, handle: thread::JoinHandle<()>) { + self.child_pool.insert(child_id, handle); + } + + pub fn join_child(&mut self, child_id: u32) { + if let Some(handle) = self.child_pool.remove(&child_id) { + let _ = handle.join(); + } + } +} diff --git a/src/types/state/worker_state.rs b/src/types/state/worker_state.rs index a03c18a1e..e056d07c7 100644 --- a/src/types/state/worker_state.rs +++ b/src/types/state/worker_state.rs @@ -1,30 +1,28 @@ use std::collections::vec_deque::Iter; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::sync::mpsc; use std::thread; -use crate::error::{AppError, AppErrorKind}; -use crate::io::{FileOperationProgress, IoWorkerObserver, IoWorkerThread}; +use crate::error::{AppError, AppErrorKind, AppResult}; use crate::types::event::AppEvent; +use crate::utils::fs::query_number_of_items; +use crate::workers::io::{FileOperationProgress, IoWorkerObserver, IoWorkerThread}; pub struct WorkerState { - // forks of applications - pub child_pool: HashMap>, // to send info pub event_tx: mpsc::Sender, // queue of IO workers pub worker_queue: VecDeque, // current worker - pub worker: Option, + pub observer: Option, } impl WorkerState { pub fn new(event_tx: mpsc::Sender) -> Self { Self { - child_pool: HashMap::new(), event_tx, worker_queue: VecDeque::new(), - worker: None, + observer: None, } } pub fn clone_event_tx(&self) -> mpsc::Sender { @@ -37,7 +35,7 @@ impl WorkerState { let _ = self.event_tx.send(AppEvent::IoWorkerCreate); } pub fn is_busy(&self) -> bool { - self.worker.is_some() + self.observer.is_some() } pub fn is_empty(&self) -> bool { self.worker_queue.is_empty() @@ -47,31 +45,31 @@ impl WorkerState { self.worker_queue.iter() } pub fn worker_ref(&self) -> Option<&IoWorkerObserver> { - self.worker.as_ref() - } - - pub fn set_progress(&mut self, res: FileOperationProgress) { - if let Some(s) = self.worker.as_mut() { - s.set_progress(res); - } + self.observer.as_ref() } pub fn get_msg(&self) -> Option<&str> { - let worker = self.worker.as_ref()?; + let worker = self.observer.as_ref()?; Some(worker.get_msg()) } - pub fn update_msg(&mut self) { - if let Some(s) = self.worker.as_mut() { - s.update_msg(); - } - } - pub fn start_next_job(&mut self) { + pub fn start_next_job(&mut self) -> AppResult<()> { let tx = self.clone_event_tx(); if let Some(worker) = self.worker_queue.pop_front() { let src = worker.paths[0].parent().unwrap().to_path_buf(); let dest = worker.dest.clone(); + let (total_files, total_bytes) = query_number_of_items(worker.paths.as_slice())?; + + let operation_progress = FileOperationProgress { + kind: worker.operation, + current_file: worker.paths[0].clone(), + total_files, + files_processed: 0, + total_bytes, + bytes_processed: 0, + }; + let handle = thread::spawn(move || { let (wtx, wrx) = mpsc::channel(); // start worker @@ -93,22 +91,13 @@ impl WorkerState { } } }); - let observer = IoWorkerObserver::new(handle, src, dest); - self.worker = Some(observer); + let observer = IoWorkerObserver::new(handle, operation_progress, src, dest); + self.observer = Some(observer); } + Ok(()) } pub fn remove_worker(&mut self) -> Option { - self.worker.take() - } - - pub fn push_child(&mut self, child_id: u32, handle: thread::JoinHandle<()>) { - self.child_pool.insert(child_id, handle); - } - - pub fn join_child(&mut self, child_id: u32) { - if let Some(handle) = self.child_pool.remove(&child_id) { - let _ = handle.join(); - } + self.observer.take() } } diff --git a/src/ui/views/tui_command_menu.rs b/src/ui/views/tui_command_menu.rs index 3101704cd..1894bb4c3 100644 --- a/src/ui/views/tui_command_menu.rs +++ b/src/ui/views/tui_command_menu.rs @@ -4,7 +4,6 @@ use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::widgets::{Clear, Widget}; -use crate::config::app::AppConfig; use crate::traits::ToString; use crate::types::keybind::KeyMapping; use crate::types::state::AppState; diff --git a/src/ui/widgets/tui_dirlist_detailed.rs b/src/ui/widgets/tui_dirlist_detailed.rs index 60f854970..e36e6ff4d 100644 --- a/src/ui/widgets/tui_dirlist_detailed.rs +++ b/src/ui/widgets/tui_dirlist_detailed.rs @@ -7,9 +7,9 @@ use ratatui::widgets::Widget; use crate::config::app::AppConfig; use crate::fs::{FileType, JoshutoDirEntry, JoshutoDirList, LinkType}; +use crate::tab::TabDisplayOption; use crate::types::option::display::DisplayOption; use crate::types::option::line_mode::{LineMode, LineModeArgs, LineNumberStyle}; -use crate::types::option::tab::TabDisplayOption; use crate::utils::format::time_to_string; use crate::utils::string::UnicodeTruncate; use crate::utils::style; diff --git a/src/ui/widgets/tui_footer.rs b/src/ui/widgets/tui_footer.rs index 89cfbd1ed..95724e5e5 100644 --- a/src/ui/widgets/tui_footer.rs +++ b/src/ui/widgets/tui_footer.rs @@ -5,7 +5,7 @@ use ratatui::text::{Line, Span}; use ratatui::widgets::{Paragraph, Widget}; use crate::fs::{JoshutoDirList, LinkType}; -use crate::types::option::tab::TabDisplayOption; +use crate::tab::TabDisplayOption; use crate::utils::format; use crate::utils::unix; use crate::{THEME_T, TIMEZONE_STR}; diff --git a/src/ui/widgets/tui_worker.rs b/src/ui/widgets/tui_worker.rs index 2af68d075..7eb608ae4 100644 --- a/src/ui/widgets/tui_worker.rs +++ b/src/ui/widgets/tui_worker.rs @@ -3,9 +3,9 @@ use ratatui::layout::Rect; use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::Widget; -use crate::io::{FileOperationProgress, IoWorkerObserver}; use crate::types::state::WorkerState; use crate::utils::format; +use crate::workers::io::IoWorkerObserver; pub struct TuiWorker<'a> { pub app_state: &'a WorkerState, @@ -23,14 +23,12 @@ impl<'a> Widget for TuiWorker<'a> { return; } match self.app_state.worker_ref() { - Some(io_obs) => { - if let Some(progress) = io_obs.progress.as_ref() { - let current_area = Rect { - y: area.y + 1, - ..area - }; - TuiCurrentWorker::new(io_obs, progress).render(current_area, buf); - } + Some(observer) => { + let current_area = Rect { + y: area.y + 1, + ..area + }; + TuiCurrentWorker::new(observer).render(current_area, buf); // draw queued up work let style = Style::default() @@ -58,12 +56,11 @@ impl<'a> Widget for TuiWorker<'a> { pub struct TuiCurrentWorker<'a> { pub observer: &'a IoWorkerObserver, - pub progress: &'a FileOperationProgress, } impl<'a> TuiCurrentWorker<'a> { - pub fn new(observer: &'a IoWorkerObserver, progress: &'a FileOperationProgress) -> Self { - Self { observer, progress } + pub fn new(observer: &'a IoWorkerObserver) -> Self { + Self { observer } } } @@ -73,40 +70,34 @@ impl<'a> Widget for TuiCurrentWorker<'a> { let left = area.left(); let right = area.right(); - let op_str = self.progress.kind().actioning_str(); + let progress = &self.observer.progress; + + let op_str = progress.kind.actioning_str(); - let processed_size = format::file_size_to_string(self.progress.bytes_processed()); - let total_size = format::file_size_to_string(self.progress.total_bytes()); + let processed_size = format::file_size_to_string(progress.bytes_processed); + let total_size = format::file_size_to_string(progress.total_bytes); let msg = format!( "{} ({}/{}) ({}/{}) {:?}", op_str, - self.progress.files_processed() + 1, - self.progress.total_files(), + progress.files_processed + 1, + progress.total_files, processed_size, total_size, self.observer.dest_path(), ); buf.set_stringn(left, top, msg, right as usize, Style::default()); - if let Some(file_name) = self - .progress - .current_file() - .file_name() - .map(|s| s.to_string_lossy()) - { - buf.set_stringn( - left, - top + 1, - format!("{}", file_name), - right as usize, - Style::default(), - ); - } + buf.set_stringn( + left, + top + 1, + format!("{}", progress.current_file.to_string_lossy()), + right as usize, + Style::default(), + ); // draw a progress bar - let progress_bar_width = (self.progress.files_processed() as f32 - / self.progress.total_files() as f32 + let progress_bar_width = (progress.files_processed as f32 / progress.total_files as f32 * area.width as f32) as usize; let progress_bar_space = " ".repeat(progress_bar_width); let progress_bar_style = Style::default().bg(Color::Blue); @@ -143,7 +134,7 @@ impl<'a> Widget for TuiWorkerQueue<'a> { let msg = format!( "{:02} {} {} items {:?}", i + 1, - worker.kind(), + worker.get_operation_type(), worker.paths.len(), worker.dest ); diff --git a/src/utils/fs.rs b/src/utils/fs.rs index c56d6811a..6a28a6ce3 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -13,6 +13,8 @@ pub fn query_number_of_items(paths: &[path::PathBuf]) -> io::Result<(usize, u64) let metadata = path.symlink_metadata()?; if metadata.is_dir() { dirs.push_back(path.clone()); + total_bytes += 1; + total_files += 1; } else { let metadata = path.symlink_metadata()?; total_bytes += metadata.len(); @@ -25,6 +27,8 @@ pub fn query_number_of_items(paths: &[path::PathBuf]) -> io::Result<(usize, u64) let path = entry?.path(); if path.is_dir() { dirs.push_back(path); + total_bytes += 1; + total_files += 1; } else { let metadata = path.symlink_metadata()?; total_bytes += metadata.len(); diff --git a/src/io/file_operation.rs b/src/workers/io/file_operation.rs similarity index 54% rename from src/io/file_operation.rs rename to src/workers/io/file_operation.rs index c6dfb74d8..c4aadca44 100644 --- a/src/io/file_operation.rs +++ b/src/workers/io/file_operation.rs @@ -58,68 +58,38 @@ impl std::fmt::Display for FileOperationOptions { } } +#[derive(Clone, Debug)] +pub enum IoWorkerProgressMessage { + FileStart { file_path: path::PathBuf }, + FileComplete { file_size: u64 }, +} + #[derive(Clone, Debug)] pub struct FileOperationProgress { - _kind: FileOperation, - _current_file: path::PathBuf, - _files_processed: usize, - _total_files: usize, - _bytes_processed: u64, - _total_bytes: u64, + pub kind: FileOperation, + pub current_file: path::PathBuf, + pub files_processed: usize, + pub total_files: usize, + pub bytes_processed: u64, + pub total_bytes: u64, } impl FileOperationProgress { pub fn new( - _kind: FileOperation, - _current_file: path::PathBuf, - _files_processed: usize, - _total_files: usize, - _bytes_processed: u64, - _total_bytes: u64, + kind: FileOperation, + current_file: path::PathBuf, + files_processed: usize, + total_files: usize, + bytes_processed: u64, + total_bytes: u64, ) -> Self { Self { - _kind, - _current_file, - _files_processed, - _total_files, - _bytes_processed, - _total_bytes, + kind, + current_file, + files_processed, + total_files, + bytes_processed, + total_bytes, } } - - pub fn kind(&self) -> FileOperation { - self._kind - } - - pub fn current_file(&self) -> &path::Path { - self._current_file.as_path() - } - - pub fn set_current_file(&mut self, current_file: path::PathBuf) { - self._current_file = current_file; - } - - pub fn files_processed(&self) -> usize { - self._files_processed - } - - pub fn set_files_processed(&mut self, files_processed: usize) { - self._files_processed = files_processed; - } - - pub fn total_files(&self) -> usize { - self._total_files - } - - pub fn bytes_processed(&self) -> u64 { - self._bytes_processed - } - - pub fn set_bytes_processed(&mut self, _bytes_processed: u64) { - self._bytes_processed = _bytes_processed; - } - - pub fn total_bytes(&self) -> u64 { - self._total_bytes - } } diff --git a/src/workers/io/io_observer.rs b/src/workers/io/io_observer.rs new file mode 100644 index 000000000..02bdb3a06 --- /dev/null +++ b/src/workers/io/io_observer.rs @@ -0,0 +1,78 @@ +use std::path; +use std::thread; + +use crate::utils::format; +use crate::workers::io::FileOperationProgress; + +use super::IoWorkerProgressMessage; + +#[derive(Debug)] +pub struct IoWorkerObserver { + pub handle: thread::JoinHandle<()>, + pub progress: FileOperationProgress, + pub msg: String, + pub src: path::PathBuf, + pub dest: path::PathBuf, +} + +impl IoWorkerObserver { + pub fn new( + handle: thread::JoinHandle<()>, + progress: FileOperationProgress, + src: path::PathBuf, + dest: path::PathBuf, + ) -> Self { + let msg = generate_worker_msg(&progress); + Self { + handle, + progress, + dest, + src, + msg, + } + } + + pub fn join(self) -> bool { + self.handle.join().is_ok() + } + + pub fn process_msg(&mut self, msg: IoWorkerProgressMessage) { + match msg { + IoWorkerProgressMessage::FileStart { file_path } => { + self.progress.current_file = file_path; + } + IoWorkerProgressMessage::FileComplete { file_size } => { + self.progress.bytes_processed += file_size; + self.progress.files_processed += 1; + } + } + } + + pub fn update_msg(&mut self) { + self.msg = generate_worker_msg(&self.progress); + } + pub fn get_msg(&self) -> &str { + self.msg.as_str() + } + pub fn src_path(&self) -> &path::Path { + self.src.as_path() + } + pub fn dest_path(&self) -> &path::Path { + self.dest.as_path() + } +} + +pub fn generate_worker_msg(progress: &FileOperationProgress) -> String { + let op_str = progress.kind.actioning_str(); + let processed_size = format::file_size_to_string(progress.bytes_processed); + let total_size = format::file_size_to_string(progress.total_bytes); + + format!( + "{} ({}/{}) ({}/{}) completed", + op_str, + progress.files_processed + 1, + progress.total_files, + processed_size, + total_size, + ) +} diff --git a/src/io/io_worker.rs b/src/workers/io/io_worker.rs similarity index 53% rename from src/io/io_worker.rs rename to src/workers/io/io_worker.rs index dd92a0ff4..863947140 100644 --- a/src/io/io_worker.rs +++ b/src/workers/io/io_worker.rs @@ -10,13 +10,13 @@ use std::os::unix; use crate::error::AppError; use crate::error::AppErrorKind; use crate::error::AppResult; -use crate::io::{FileOperation, FileOperationOptions, FileOperationProgress}; -use crate::utils::fs::query_number_of_items; use crate::utils::name_resolution::rename_filename_conflict; +use super::{FileOperation, FileOperationOptions, IoWorkerProgressMessage}; + #[derive(Clone, Debug)] pub struct IoWorkerThread { - _kind: FileOperation, + pub operation: FileOperation, pub options: FileOperationOptions, pub paths: Vec, pub dest: path::PathBuf, @@ -24,28 +24,25 @@ pub struct IoWorkerThread { impl IoWorkerThread { pub fn new( - _kind: FileOperation, + operation: FileOperation, paths: Vec, dest: path::PathBuf, options: FileOperationOptions, ) -> Self { Self { - _kind, + operation, options, paths, dest, } } - pub fn kind(&self) -> FileOperation { - self._kind + pub fn get_operation_type(&self) -> FileOperation { + self.operation } - pub fn start( - &self, - tx: mpsc::Sender, - ) -> AppResult { - match self.kind() { + pub fn start(&self, tx: mpsc::Sender) -> AppResult<()> { + match self.get_operation_type() { FileOperation::Cut => self.paste_cut(tx), FileOperation::Copy => self.paste_copy(tx), FileOperation::Symlink { relative: false } => self.paste_link_absolute(tx), @@ -54,76 +51,26 @@ impl IoWorkerThread { } } - fn paste_copy( - &self, - tx: mpsc::Sender, - ) -> AppResult { - let (total_files, total_bytes) = query_number_of_items(&self.paths)?; - let mut progress = FileOperationProgress::new( - self.kind(), - self.paths[0].to_path_buf(), - 0, - total_files, - 0, - total_bytes, - ); + fn paste_copy(&self, tx: mpsc::Sender) -> AppResult<()> { for path in self.paths.iter() { - let _ = tx.send(progress.clone()); - recursive_copy( - &tx, - path.as_path(), - self.dest.as_path(), - self.options, - &mut progress, - )?; + recursive_copy(&tx, path.as_path(), self.dest.as_path(), self.options)?; } - Ok(progress) + Ok(()) } - fn paste_cut( - &self, - tx: mpsc::Sender, - ) -> AppResult { - let (total_files, total_bytes) = query_number_of_items(&self.paths)?; - let mut progress = FileOperationProgress::new( - self.kind(), - self.paths[0].to_path_buf(), - 0, - total_files, - 0, - total_bytes, - ); + fn paste_cut(&self, tx: mpsc::Sender) -> AppResult<()> { for path in self.paths.iter() { - let _ = tx.send(progress.clone()); - recursive_cut( - &tx, - path.as_path(), - self.dest.as_path(), - self.options, - &mut progress, - )?; + recursive_cut(&tx, path.as_path(), self.dest.as_path(), self.options)?; } - Ok(progress) + Ok(()) } - fn paste_link_absolute( - &self, - tx: mpsc::Sender, - ) -> AppResult { - let total_files = self.paths.len(); - let total_bytes = total_files as u64; - let mut progress = FileOperationProgress::new( - self.kind(), - self.paths[0].to_path_buf(), - 0, - total_files, - 0, - total_bytes, - ); - + fn paste_link_absolute(&self, tx: mpsc::Sender) -> AppResult<()> { #[cfg(unix)] for src in self.paths.iter() { - let _ = tx.send(progress.clone()); + let _ = tx.send(IoWorkerProgressMessage::FileStart { + file_path: src.to_path_buf(), + }); let mut dest_buf = self.dest.to_path_buf(); if let Some(s) = src.file_name() { dest_buf.push(s); @@ -132,30 +79,17 @@ impl IoWorkerThread { rename_filename_conflict(&mut dest_buf); } unix::fs::symlink(src, &dest_buf)?; - progress.set_files_processed(progress.files_processed() + 1); - progress.set_bytes_processed(progress.bytes_processed() + 1); + let _ = tx.send(IoWorkerProgressMessage::FileComplete { file_size: 1 }); } - Ok(progress) + Ok(()) } - fn paste_link_relative( - &self, - tx: mpsc::Sender, - ) -> AppResult { - let total_files = self.paths.len(); - let total_bytes = total_files as u64; - let mut progress = FileOperationProgress::new( - self.kind(), - self.paths[0].to_path_buf(), - 0, - total_files, - 0, - total_bytes, - ); - + fn paste_link_relative(&self, tx: mpsc::Sender) -> AppResult<()> { #[cfg(unix)] for src in self.paths.iter() { - let _ = tx.send(progress.clone()); + let _ = tx.send(IoWorkerProgressMessage::FileStart { + file_path: src.to_path_buf(), + }); let mut dest_buf = self.dest.to_path_buf(); if let Some(s) = src.file_name() { dest_buf.push(s); @@ -185,40 +119,31 @@ impl IoWorkerThread { } unix::fs::symlink(relative_path, &dest_buf)?; - progress.set_files_processed(progress.files_processed() + 1); - progress.set_bytes_processed(progress.bytes_processed() + 1); + let _ = tx.send(IoWorkerProgressMessage::FileComplete { file_size: 1 }); } - Ok(progress) + Ok(()) } - fn delete(&self, _tx: mpsc::Sender) -> AppResult { - let (total_files, total_bytes) = query_number_of_items(&self.paths)?; - let progress = FileOperationProgress::new( - self.kind(), - self.paths[0].to_path_buf(), - total_files, - total_files, - total_bytes, - total_bytes, - ); - + fn delete(&self, tx: mpsc::Sender) -> AppResult<()> { if self.options.permanently { - remove_files(&self.paths)?; + remove_files(&self.paths, tx)?; } else { - trash_files(&self.paths)?; + trash_files(&self.paths, tx)?; } - - Ok(progress) + Ok(()) } } pub fn recursive_copy( - tx: &mpsc::Sender, + tx: &mpsc::Sender, src: &path::Path, dest: &path::Path, options: FileOperationOptions, - progress: &mut FileOperationProgress, ) -> io::Result<()> { + let _ = tx.send(IoWorkerProgressMessage::FileStart { + file_path: src.to_path_buf(), + }); + let mut dest_buf = dest.to_path_buf(); if let Some(s) = src.file_name() { dest_buf.push(s); @@ -227,8 +152,6 @@ pub fn recursive_copy( rename_filename_conflict(&mut dest_buf); } - progress.set_current_file(src.to_path_buf()); - let file_type = fs::symlink_metadata(src)?.file_type(); if file_type.is_dir() { match fs::create_dir(dest_buf.as_path()) { @@ -242,25 +165,20 @@ pub fn recursive_copy( for entry in fs::read_dir(src)? { let entry = entry?; let entry_path = entry.path(); - recursive_copy( - tx, - entry_path.as_path(), - dest_buf.as_path(), - options, - progress, - )?; - let _ = tx.send(progress.clone()); + recursive_copy(tx, entry_path.as_path(), dest_buf.as_path(), options)?; } + let _ = tx.send(IoWorkerProgressMessage::FileComplete { file_size: 1 }); Ok(()) } else if file_type.is_file() { - let bytes_processed = progress.bytes_processed() + fs::copy(src, dest_buf)?; - progress.set_bytes_processed(bytes_processed); - progress.set_files_processed(progress.files_processed() + 1); + let bytes_processed = fs::copy(src, dest_buf)?; + let _ = tx.send(IoWorkerProgressMessage::FileComplete { + file_size: bytes_processed, + }); Ok(()) } else if file_type.is_symlink() { let link_path = fs::read_link(src)?; std::os::unix::fs::symlink(link_path, dest_buf)?; - progress.set_files_processed(progress.files_processed() + 1); + let _ = tx.send(IoWorkerProgressMessage::FileComplete { file_size: 1 }); Ok(()) } else { Ok(()) @@ -268,12 +186,15 @@ pub fn recursive_copy( } pub fn recursive_cut( - tx: &mpsc::Sender, + tx: &mpsc::Sender, src: &path::Path, dest: &path::Path, options: FileOperationOptions, - progress: &mut FileOperationProgress, ) -> io::Result<()> { + let _ = tx.send(IoWorkerProgressMessage::FileStart { + file_path: src.to_path_buf(), + }); + let mut dest_buf = dest.to_path_buf(); if let Some(s) = src.file_name() { dest_buf.push(s); @@ -284,13 +205,12 @@ pub fn recursive_cut( let metadata = fs::symlink_metadata(src)?; let file_type = metadata.file_type(); - progress.set_current_file(src.to_path_buf()); - match fs::rename(src, dest_buf.as_path()) { Ok(_) => { - let bytes_processed = progress.bytes_processed() + metadata.len(); - progress.set_bytes_processed(bytes_processed); - progress.set_files_processed(progress.files_processed() + 1); + let bytes_processed = metadata.len(); + let _ = tx.send(IoWorkerProgressMessage::FileComplete { + file_size: bytes_processed, + }); Ok(()) } Err(_e) => { @@ -298,56 +218,65 @@ pub fn recursive_cut( fs::create_dir(dest_buf.as_path())?; for entry in fs::read_dir(src)? { let entry_path = entry?.path(); - recursive_cut( - tx, - entry_path.as_path(), - dest_buf.as_path(), - options, - progress, - )?; - let _ = tx.send(progress.clone()); + recursive_cut(tx, entry_path.as_path(), dest_buf.as_path(), options)?; } fs::remove_dir(src)?; + let _ = tx.send(IoWorkerProgressMessage::FileComplete { file_size: 1 }); } else if file_type.is_symlink() { let link_path = fs::read_link(src)?; std::os::unix::fs::symlink(link_path, dest_buf)?; fs::remove_file(src)?; - let processed = progress.bytes_processed() + metadata.len(); - progress.set_bytes_processed(processed); - progress.set_files_processed(progress.files_processed() + 1); + + let bytes_processed = metadata.len(); + let _ = tx.send(IoWorkerProgressMessage::FileComplete { + file_size: bytes_processed, + }); } else { - let processed = progress.bytes_processed() + fs::copy(src, dest_buf.as_path())?; + let bytes_processed = fs::copy(src, dest_buf.as_path())?; fs::remove_file(src)?; - progress.set_bytes_processed(processed); - progress.set_files_processed(progress.files_processed() + 1); + + let _ = tx.send(IoWorkerProgressMessage::FileComplete { + file_size: bytes_processed, + }); } Ok(()) } } } -fn remove_files

(paths: &[P]) -> std::io::Result<()> +fn remove_files

(paths: &[P], tx: mpsc::Sender) -> std::io::Result<()> where P: AsRef, { for path in paths { if let Ok(metadata) = fs::symlink_metadata(path) { + let _ = tx.send(IoWorkerProgressMessage::FileStart { + file_path: path.as_ref().to_path_buf(), + }); if metadata.is_dir() { fs::remove_dir_all(path)?; } else { fs::remove_file(path)?; } + let bytes_processed = metadata.len(); + let _ = tx.send(IoWorkerProgressMessage::FileComplete { + file_size: bytes_processed, + }); } } Ok(()) } -fn trash_files

(paths: &[P]) -> AppResult +fn trash_files

(paths: &[P], tx: mpsc::Sender) -> AppResult where P: AsRef, { for path in paths { + let _ = tx.send(IoWorkerProgressMessage::FileStart { + file_path: path.as_ref().to_path_buf(), + }); trash_file(path)?; + let _ = tx.send(IoWorkerProgressMessage::FileComplete { file_size: 1 }); } Ok(()) } diff --git a/src/io/mod.rs b/src/workers/io/mod.rs similarity index 100% rename from src/io/mod.rs rename to src/workers/io/mod.rs diff --git a/src/workers/mod.rs b/src/workers/mod.rs new file mode 100644 index 000000000..af514a1ee --- /dev/null +++ b/src/workers/mod.rs @@ -0,0 +1 @@ +pub mod io;