diff --git a/src/activity_manager.rs b/src/activity_manager.rs index 133481e..3a852c6 100644 --- a/src/activity_manager.rs +++ b/src/activity_manager.rs @@ -56,6 +56,7 @@ pub enum NextActivity { pub struct ActivityManager { context: Option, interval: Duration, + local_dir: PathBuf, } impl ActivityManager { @@ -64,19 +65,16 @@ impl ActivityManager { /// Initializes a new Activity Manager pub fn new(local_dir: &Path, interval: Duration) -> Result { // Prepare Context - let host: Localhost = match Localhost::new(local_dir.to_path_buf()) { - Ok(h) => h, - Err(e) => return Err(e), - }; // Initialize configuration client let (config_client, error): (Option, Option) = match Self::init_config_client() { Ok(cli) => (Some(cli), None), Err(err) => (None, Some(err)), }; - let ctx: Context = Context::new(host, config_client, error); + let ctx: Context = Context::new(config_client, error); Ok(ActivityManager { context: Some(ctx), + local_dir: local_dir.to_path_buf(), interval, }) } @@ -182,7 +180,7 @@ impl ActivityManager { /// Returns the next activity to run fn run_filetransfer(&mut self) -> Option { // Get context - let ctx: Context = match self.context.take() { + let mut ctx: Context = match self.context.take() { Some(ctx) => ctx, None => return None, }; @@ -193,7 +191,15 @@ impl ActivityManager { }; // Prepare activity let protocol: FileTransferProtocol = ft_params.protocol; - let mut activity: FileTransferActivity = FileTransferActivity::new(protocol); + let host: Localhost = match Localhost::new(self.local_dir.clone()) { + Ok(host) => host, + Err(err) => { + // Set error in context + ctx.set_error(format!("Could not initialize localhost: {}", err)); + return None; + } + }; + let mut activity: FileTransferActivity = FileTransferActivity::new(host, protocol); // Prepare result let result: Option; // Create activity diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 2f8e4a6..1d7d6e7 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -185,9 +185,8 @@ impl FtpFileTransfer { return Err(()); } // Get symlink - let symlink: Option> = match symlink_path { - None => None, - Some(p) => Some(Box::new(match p.to_string_lossy().ends_with('/') { + let symlink: Option> = symlink_path.map(|p| { + Box::new(match p.to_string_lossy().ends_with('/') { true => { // NOTE: is_dir becomes true is_dir = true; @@ -226,8 +225,8 @@ impl FtpFileTransfer { group: gid, unix_pex: Some(unix_pex), }), - })), - }; + }) + }); let mut abs_path: PathBuf = PathBuf::from(path); abs_path.push(file_name.as_str()); let abs_path: PathBuf = Self::resolve(abs_path.as_path()); diff --git a/src/ui/activities/filetransfer_activity/actions.rs b/src/ui/activities/filetransfer_activity/actions.rs deleted file mode 100644 index e1bdd4a..0000000 --- a/src/ui/activities/filetransfer_activity/actions.rs +++ /dev/null @@ -1,760 +0,0 @@ -//! ## FileTransferActivity -//! -//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall - -/** - * MIT License - * - * termscp - Copyright (c) 2021 Christian Visintin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -// locals -use super::{FileExplorerTab, FileTransferActivity, FsEntry, LogLevel}; -use tuirealm::{Payload, Value}; -// externals -use std::path::PathBuf; - -impl FileTransferActivity { - /// ### action_enter_local_dir - /// - /// Enter a directory on local host from entry - /// Return true whether the directory changed - pub(super) fn action_enter_local_dir(&mut self, entry: FsEntry, block_sync: bool) -> bool { - match entry { - FsEntry::Directory(dir) => { - self.local_changedir(dir.abs_path.as_path(), true); - if self.browser.sync_browsing && !block_sync { - self.action_change_remote_dir(dir.name, true); - } - true - } - FsEntry::File(file) => { - match &file.symlink { - Some(symlink_entry) => { - // If symlink and is directory, point to symlink - match &**symlink_entry { - FsEntry::Directory(dir) => { - self.local_changedir(dir.abs_path.as_path(), true); - // Check whether to sync - if self.browser.sync_browsing && !block_sync { - self.action_change_remote_dir(dir.name.clone(), true); - } - true - } - _ => false, - } - } - None => false, - } - } - } - } - - /// ### action_enter_remote_dir - /// - /// Enter a directory on local host from entry - /// Return true whether the directory changed - pub(super) fn action_enter_remote_dir(&mut self, entry: FsEntry, block_sync: bool) -> bool { - match entry { - FsEntry::Directory(dir) => { - self.remote_changedir(dir.abs_path.as_path(), true); - if self.browser.sync_browsing && !block_sync { - self.action_change_local_dir(dir.name, true); - } - true - } - FsEntry::File(file) => { - match &file.symlink { - Some(symlink_entry) => { - // If symlink and is directory, point to symlink - match &**symlink_entry { - FsEntry::Directory(dir) => { - self.remote_changedir(dir.abs_path.as_path(), true); - // Check whether to sync - if self.browser.sync_browsing && !block_sync { - self.action_change_local_dir(dir.name.clone(), true); - } - true - } - _ => false, - } - } - None => false, - } - } - } - } - - /// ### action_change_local_dir - /// - /// Change local directory reading value from input - pub(super) fn action_change_local_dir(&mut self, input: String, block_sync: bool) { - let dir_path: PathBuf = self.local_to_abs_path(PathBuf::from(input.as_str()).as_path()); - self.local_changedir(dir_path.as_path(), true); - // Check whether to sync - if self.browser.sync_browsing && !block_sync { - self.action_change_remote_dir(input, true); - } - } - - /// ### action_change_remote_dir - /// - /// Change remote directory reading value from input - pub(super) fn action_change_remote_dir(&mut self, input: String, block_sync: bool) { - let dir_path: PathBuf = self.remote_to_abs_path(PathBuf::from(input.as_str()).as_path()); - self.remote_changedir(dir_path.as_path(), true); - // Check whether to sync - if self.browser.sync_browsing && !block_sync { - self.action_change_local_dir(input, true); - } - } - - /// ### action_go_to_previous_local_dir - /// - /// Go to previous directory from localhost - pub(super) fn action_go_to_previous_local_dir(&mut self, block_sync: bool) { - if let Some(d) = self.local.popd() { - self.local_changedir(d.as_path(), false); - // Check whether to sync - if self.browser.sync_browsing && !block_sync { - self.action_go_to_previous_remote_dir(true); - } - } - } - - /// ### action_go_to_previous_remote_dir - /// - /// Go to previous directory from remote host - pub(super) fn action_go_to_previous_remote_dir(&mut self, block_sync: bool) { - if let Some(d) = self.remote.popd() { - self.remote_changedir(d.as_path(), false); - // Check whether to sync - if self.browser.sync_browsing && !block_sync { - self.action_go_to_previous_local_dir(true); - } - } - } - - /// ### action_go_to_local_upper_dir - /// - /// Go to upper directory on local host - pub(super) fn action_go_to_local_upper_dir(&mut self, block_sync: bool) { - // Get pwd - let path: PathBuf = self.local.wrkdir.clone(); - // Go to parent directory - if let Some(parent) = path.as_path().parent() { - self.local_changedir(parent, true); - // If sync is enabled update remote too - if self.browser.sync_browsing && !block_sync { - self.action_go_to_remote_upper_dir(true); - } - } - } - - /// #### action_go_to_remote_upper_dir - /// - /// Go to upper directory on remote host - pub(super) fn action_go_to_remote_upper_dir(&mut self, block_sync: bool) { - // Get pwd - let path: PathBuf = self.remote.wrkdir.clone(); - // Go to parent directory - if let Some(parent) = path.as_path().parent() { - self.remote_changedir(parent, true); - // If sync is enabled update local too - if self.browser.sync_browsing && !block_sync { - self.action_go_to_local_upper_dir(true); - } - } - } - - /// ### action_local_copy - /// - /// Copy file on local - pub(super) fn action_local_copy(&mut self, input: String) { - if let Some(idx) = self.get_local_file_idx() { - let dest_path: PathBuf = PathBuf::from(input); - let entry: FsEntry = self.local.get(idx).unwrap().clone(); - if let Some(ctx) = self.context.as_mut() { - match ctx.local.copy(&entry, dest_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, - format!( - "Copied \"{}\" to \"{}\"", - entry.get_abs_path().display(), - dest_path.display() - ) - .as_str(), - ); - // Reload entries - let wrkdir: PathBuf = self.local.wrkdir.clone(); - self.local_scan(wrkdir.as_path()); - } - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not copy \"{}\" to \"{}\": {}", - entry.get_abs_path().display(), - dest_path.display(), - err - ), - ), - } - } - } - } - - /// ### action_remote_copy - /// - /// Copy file on remote - pub(super) fn action_remote_copy(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_idx() { - let dest_path: PathBuf = PathBuf::from(input); - let entry: FsEntry = self.remote.get(idx).unwrap().clone(); - match self.client.as_mut().copy(&entry, dest_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, - format!( - "Copied \"{}\" to \"{}\"", - entry.get_abs_path().display(), - dest_path.display() - ) - .as_str(), - ); - self.reload_remote_dir(); - } - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not copy \"{}\" to \"{}\": {}", - entry.get_abs_path().display(), - dest_path.display(), - err - ), - ), - } - } - } - - pub(super) fn action_local_mkdir(&mut self, input: String) { - match self - .context - .as_mut() - .unwrap() - .local - .mkdir(PathBuf::from(input.as_str()).as_path()) - { - Ok(_) => { - // Reload files - self.log( - LogLevel::Info, - format!("Created directory \"{}\"", input).as_ref(), - ); - let wrkdir: PathBuf = self.local.wrkdir.clone(); - self.local_scan(wrkdir.as_path()); - } - Err(err) => { - // Report err - self.log_and_alert( - LogLevel::Error, - format!("Could not create directory \"{}\": {}", input, err), - ); - } - } - } - pub(super) fn action_remote_mkdir(&mut self, input: String) { - match self - .client - .as_mut() - .mkdir(PathBuf::from(input.as_str()).as_path()) - { - Ok(_) => { - // Reload files - self.log( - LogLevel::Info, - format!("Created directory \"{}\"", input).as_ref(), - ); - self.reload_remote_dir(); - } - Err(err) => { - // Report err - self.log_and_alert( - LogLevel::Error, - format!("Could not create directory \"{}\": {}", input, err), - ); - } - } - } - - pub(super) fn action_local_rename(&mut self, input: String) { - let entry: Option = self.get_local_file_entry().cloned(); - if let Some(entry) = entry { - let mut dst_path: PathBuf = PathBuf::from(input); - // Check if path is relative - if dst_path.as_path().is_relative() { - let mut wrkdir: PathBuf = self.local.wrkdir.clone(); - wrkdir.push(dst_path); - dst_path = wrkdir; - } - let full_path: PathBuf = entry.get_abs_path(); - // Rename file or directory and report status as popup - match self - .context - .as_mut() - .unwrap() - .local - .rename(&entry, dst_path.as_path()) - { - Ok(_) => { - // Reload files - let path: PathBuf = self.local.wrkdir.clone(); - self.local_scan(path.as_path()); - // Log - self.log( - LogLevel::Info, - format!( - "Renamed file \"{}\" to \"{}\"", - full_path.display(), - dst_path.display() - ) - .as_ref(), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not rename file \"{}\": {}", full_path.display(), err), - ); - } - } - } - } - - pub(super) fn action_remote_rename(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_idx() { - if let Some(entry) = self.remote.get(idx) { - let dst_path: PathBuf = PathBuf::from(input); - let full_path: PathBuf = entry.get_abs_path(); - // Rename file or directory and report status as popup - match self.client.as_mut().rename(entry, dst_path.as_path()) { - Ok(_) => { - // Reload files - let path: PathBuf = self.remote.wrkdir.clone(); - self.remote_scan(path.as_path()); - // Log - self.log( - LogLevel::Info, - format!( - "Renamed file \"{}\" to \"{}\"", - full_path.display(), - dst_path.display() - ) - .as_ref(), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not rename file \"{}\": {}", full_path.display(), err), - ); - } - } - } - } - } - - pub(super) fn action_local_delete(&mut self) { - let entry: Option = self.get_local_file_entry().cloned(); - if let Some(entry) = entry { - let full_path: PathBuf = entry.get_abs_path(); - // Delete file or directory and report status as popup - match self.context.as_mut().unwrap().local.remove(&entry) { - Ok(_) => { - // Reload files - let p: PathBuf = self.local.wrkdir.clone(); - self.local_scan(p.as_path()); - // Log - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()).as_ref(), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not delete file \"{}\": {}", full_path.display(), err), - ); - } - } - } - } - - pub(super) fn action_remote_delete(&mut self) { - if let Some(idx) = self.get_remote_file_idx() { - // Check if file entry exists - if let Some(entry) = self.remote.get(idx) { - let full_path: PathBuf = entry.get_abs_path(); - // Delete file - match self.client.remove(entry) { - Ok(_) => { - self.reload_remote_dir(); - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()).as_ref(), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not delete file \"{}\": {}", full_path.display(), err), - ); - } - } - } - } - } - - pub(super) fn action_local_saveas(&mut self, input: String) { - if let Some(idx) = self.get_local_file_idx() { - // Get pwd - let wrkdir: PathBuf = self.remote.wrkdir.clone(); - if self.local.get(idx).is_some() { - let file: FsEntry = self.local.get(idx).unwrap().clone(); - // Call upload; pass realfile, keep link name - self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(input)); - } - } - } - - pub(super) fn action_remote_saveas(&mut self, input: String) { - if let Some(idx) = self.get_remote_file_idx() { - // Get pwd - let wrkdir: PathBuf = self.local.wrkdir.clone(); - if self.remote.get(idx).is_some() { - let file: FsEntry = self.remote.get(idx).unwrap().clone(); - // Call upload; pass realfile, keep link name - self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(input)); - } - } - } - - pub(super) fn action_local_newfile(&mut self, input: String) { - // Check if file exists - let mut file_exists: bool = false; - for file in self.local.iter_files_all() { - if input == file.get_name() { - file_exists = true; - } - } - if file_exists { - self.log_and_alert( - LogLevel::Warn, - format!("File \"{}\" already exists", input,), - ); - return; - } - // Create file - let file_path: PathBuf = PathBuf::from(input.as_str()); - if let Some(ctx) = self.context.as_mut() { - if let Err(err) = ctx.local.open_file_write(file_path.as_path()) { - self.log_and_alert( - LogLevel::Error, - format!("Could not create file \"{}\": {}", file_path.display(), err), - ); - } else { - self.log( - LogLevel::Info, - format!("Created file \"{}\"", file_path.display()).as_str(), - ); - } - // Reload files - let path: PathBuf = self.local.wrkdir.clone(); - self.local_scan(path.as_path()); - } - } - - pub(super) fn action_remote_newfile(&mut self, input: String) { - // Check if file exists - let mut file_exists: bool = false; - for file in self.remote.iter_files_all() { - if input == file.get_name() { - file_exists = true; - } - } - if file_exists { - self.log_and_alert( - LogLevel::Warn, - format!("File \"{}\" already exists", input,), - ); - return; - } - // Get path on remote - let file_path: PathBuf = PathBuf::from(input.as_str()); - // Create file (on local) - match tempfile::NamedTempFile::new() { - Err(err) => self.log_and_alert( - LogLevel::Error, - format!("Could not create tempfile: {}", err), - ), - Ok(tfile) => { - // Stat tempfile - if let Some(ctx) = self.context.as_mut() { - let local_file: FsEntry = match ctx.local.stat(tfile.path()) { - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Could not stat tempfile: {}", err), - ); - return; - } - Ok(f) => f, - }; - if let FsEntry::File(local_file) = local_file { - // Create file - match self.client.send_file(&local_file, file_path.as_path()) { - Err(err) => self.log_and_alert( - LogLevel::Error, - format!( - "Could not create file \"{}\": {}", - file_path.display(), - err - ), - ), - Ok(writer) => { - // Finalize write - if let Err(err) = self.client.on_sent(writer) { - self.log_and_alert( - LogLevel::Warn, - format!("Could not finalize file: {}", err), - ); - } else { - self.log( - LogLevel::Info, - format!("Created file \"{}\"", file_path.display()) - .as_str(), - ); - } - // Reload files - let path: PathBuf = self.remote.wrkdir.clone(); - self.remote_scan(path.as_path()); - } - } - } - } - } - } - } - - pub(super) fn action_local_exec(&mut self, input: String) { - match self.context.as_mut().unwrap().local.exec(input.as_str()) { - Ok(output) => { - // Reload files - self.log( - LogLevel::Info, - format!("\"{}\": {}", input, output).as_ref(), - ); - let wrkdir: PathBuf = self.local.wrkdir.clone(); - self.local_scan(wrkdir.as_path()); - } - Err(err) => { - // Report err - self.log_and_alert( - LogLevel::Error, - format!("Could not execute command \"{}\": {}", input, err), - ); - } - } - } - - pub(super) fn action_remote_exec(&mut self, input: String) { - match self.client.as_mut().exec(input.as_str()) { - Ok(output) => { - // Reload files - self.log( - LogLevel::Info, - format!("\"{}\": {}", input, output).as_ref(), - ); - self.reload_remote_dir(); - } - Err(err) => { - // Report err - self.log_and_alert( - LogLevel::Error, - format!("Could not execute command \"{}\": {}", input, err), - ); - } - } - } - - pub(super) fn action_local_find(&mut self, input: String) -> Result, String> { - match self.context.as_mut().unwrap().local.find(input.as_str()) { - Ok(entries) => Ok(entries), - Err(err) => Err(format!("Could not search for files: {}", err)), - } - } - - pub(super) fn action_remote_find(&mut self, input: String) -> Result, String> { - match self.client.as_mut().find(input.as_str()) { - Ok(entries) => Ok(entries), - Err(err) => Err(format!("Could not search for files: {}", err)), - } - } - - pub(super) fn action_find_changedir(&mut self, idx: usize) { - // Match entry - if let Some(entry) = self.found.as_ref().unwrap().get(idx) { - // Get path: if a directory, use directory path; if it is a File, get parent path - let path: PathBuf = match entry { - FsEntry::Directory(dir) => dir.abs_path.clone(), - FsEntry::File(file) => match file.abs_path.parent() { - None => PathBuf::from("."), - Some(p) => p.to_path_buf(), - }, - }; - // Change directory - match self.tab { - FileExplorerTab::FindLocal | FileExplorerTab::Local => { - self.local_changedir(path.as_path(), true) - } - FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - self.remote_changedir(path.as_path(), true) - } - } - } - } - - pub(super) fn action_find_transfer(&mut self, idx: usize, name: Option) { - let entry: Option = self.found.as_ref().unwrap().get(idx).cloned(); - if let Some(entry) = entry { - // Download file - match self.tab { - FileExplorerTab::FindLocal | FileExplorerTab::Local => { - let wrkdir: PathBuf = self.remote.wrkdir.clone(); - self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), name); - } - FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - let wrkdir: PathBuf = self.local.wrkdir.clone(); - self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), name); - } - } - } - } - - pub(super) fn action_find_delete(&mut self, idx: usize) { - let entry: Option = self.found.as_ref().unwrap().get(idx).cloned(); - if let Some(entry) = entry { - // Download file - match self.tab { - FileExplorerTab::FindLocal | FileExplorerTab::Local => { - let full_path: PathBuf = entry.get_abs_path(); - // Delete file or directory and report status as popup - match self.context.as_mut().unwrap().local.remove(&entry) { - Ok(_) => { - // Reload files - let p: PathBuf = self.local.wrkdir.clone(); - self.local_scan(p.as_path()); - // Log - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()).as_ref(), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not delete file \"{}\": {}", - full_path.display(), - err - ), - ); - } - } - } - FileExplorerTab::FindRemote | FileExplorerTab::Remote => { - let full_path: PathBuf = entry.get_abs_path(); - // Delete file - match self.client.remove(&entry) { - Ok(_) => { - self.reload_remote_dir(); - self.log( - LogLevel::Info, - format!("Removed file \"{}\"", full_path.display()).as_ref(), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not delete file \"{}\": {}", - full_path.display(), - err - ), - ); - } - } - } - } - } - } - - /// ### get_local_file_entry - /// - /// Get local file entry - pub(super) fn get_local_file_entry(&self) -> Option<&FsEntry> { - match self.get_local_file_idx() { - None => None, - Some(idx) => self.local.get(idx), - } - } - - /// ### get_remote_file_entry - /// - /// Get remote file entry - pub(super) fn get_remote_file_entry(&self) -> Option<&FsEntry> { - match self.get_remote_file_idx() { - None => None, - Some(idx) => self.remote.get(idx), - } - } - - // -- private - - /// ### get_local_file_idx - /// - /// Get index of selected file in the local tab - fn get_local_file_idx(&self) -> Option { - match self.view.get_state(super::COMPONENT_EXPLORER_LOCAL) { - Some(Payload::One(Value::Usize(idx))) => Some(idx), - _ => None, - } - } - - /// ### get_remote_file_idx - /// - /// Get index of selected file in the remote file - fn get_remote_file_idx(&self) -> Option { - match self.view.get_state(super::COMPONENT_EXPLORER_REMOTE) { - Some(Payload::One(Value::Usize(idx))) => Some(idx), - _ => None, - } - } -} diff --git a/src/ui/activities/filetransfer_activity/actions/change_dir.rs b/src/ui/activities/filetransfer_activity/actions/change_dir.rs new file mode 100644 index 0000000..ad7ad09 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/change_dir.rs @@ -0,0 +1,184 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry}; +use std::path::PathBuf; + +impl FileTransferActivity { + /// ### action_enter_local_dir + /// + /// Enter a directory on local host from entry + /// Return true whether the directory changed + pub(crate) fn action_enter_local_dir(&mut self, entry: FsEntry, block_sync: bool) -> bool { + match entry { + FsEntry::Directory(dir) => { + self.local_changedir(dir.abs_path.as_path(), true); + if self.browser.sync_browsing && !block_sync { + self.action_change_remote_dir(dir.name, true); + } + true + } + FsEntry::File(file) => { + match &file.symlink { + Some(symlink_entry) => { + // If symlink and is directory, point to symlink + match &**symlink_entry { + FsEntry::Directory(dir) => { + self.local_changedir(dir.abs_path.as_path(), true); + // Check whether to sync + if self.browser.sync_browsing && !block_sync { + self.action_change_remote_dir(dir.name.clone(), true); + } + true + } + _ => false, + } + } + None => false, + } + } + } + } + + /// ### action_enter_remote_dir + /// + /// Enter a directory on local host from entry + /// Return true whether the directory changed + pub(crate) fn action_enter_remote_dir(&mut self, entry: FsEntry, block_sync: bool) -> bool { + match entry { + FsEntry::Directory(dir) => { + self.remote_changedir(dir.abs_path.as_path(), true); + if self.browser.sync_browsing && !block_sync { + self.action_change_local_dir(dir.name, true); + } + true + } + FsEntry::File(file) => { + match &file.symlink { + Some(symlink_entry) => { + // If symlink and is directory, point to symlink + match &**symlink_entry { + FsEntry::Directory(dir) => { + self.remote_changedir(dir.abs_path.as_path(), true); + // Check whether to sync + if self.browser.sync_browsing && !block_sync { + self.action_change_local_dir(dir.name.clone(), true); + } + true + } + _ => false, + } + } + None => false, + } + } + } + } + + /// ### action_change_local_dir + /// + /// Change local directory reading value from input + pub(crate) fn action_change_local_dir(&mut self, input: String, block_sync: bool) { + let dir_path: PathBuf = self.local_to_abs_path(PathBuf::from(input.as_str()).as_path()); + self.local_changedir(dir_path.as_path(), true); + // Check whether to sync + if self.browser.sync_browsing && !block_sync { + self.action_change_remote_dir(input, true); + } + } + + /// ### action_change_remote_dir + /// + /// Change remote directory reading value from input + pub(crate) fn action_change_remote_dir(&mut self, input: String, block_sync: bool) { + let dir_path: PathBuf = self.remote_to_abs_path(PathBuf::from(input.as_str()).as_path()); + self.remote_changedir(dir_path.as_path(), true); + // Check whether to sync + if self.browser.sync_browsing && !block_sync { + self.action_change_local_dir(input, true); + } + } + + /// ### action_go_to_previous_local_dir + /// + /// Go to previous directory from localhost + pub(crate) fn action_go_to_previous_local_dir(&mut self, block_sync: bool) { + if let Some(d) = self.local_mut().popd() { + self.local_changedir(d.as_path(), false); + // Check whether to sync + if self.browser.sync_browsing && !block_sync { + self.action_go_to_previous_remote_dir(true); + } + } + } + + /// ### action_go_to_previous_remote_dir + /// + /// Go to previous directory from remote host + pub(crate) fn action_go_to_previous_remote_dir(&mut self, block_sync: bool) { + if let Some(d) = self.remote_mut().popd() { + self.remote_changedir(d.as_path(), false); + // Check whether to sync + if self.browser.sync_browsing && !block_sync { + self.action_go_to_previous_local_dir(true); + } + } + } + + /// ### action_go_to_local_upper_dir + /// + /// Go to upper directory on local host + pub(crate) fn action_go_to_local_upper_dir(&mut self, block_sync: bool) { + // Get pwd + let path: PathBuf = self.local().wrkdir.clone(); + // Go to parent directory + if let Some(parent) = path.as_path().parent() { + self.local_changedir(parent, true); + // If sync is enabled update remote too + if self.browser.sync_browsing && !block_sync { + self.action_go_to_remote_upper_dir(true); + } + } + } + + /// #### action_go_to_remote_upper_dir + /// + /// Go to upper directory on remote host + pub(crate) fn action_go_to_remote_upper_dir(&mut self, block_sync: bool) { + // Get pwd + let path: PathBuf = self.remote().wrkdir.clone(); + // Go to parent directory + if let Some(parent) = path.as_path().parent() { + self.remote_changedir(parent, true); + // If sync is enabled update local too + if self.browser.sync_browsing && !block_sync { + self.action_go_to_local_upper_dir(true); + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/copy.rs b/src/ui/activities/filetransfer_activity/actions/copy.rs new file mode 100644 index 0000000..3b3d7c4 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/copy.rs @@ -0,0 +1,98 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + /// ### action_local_copy + /// + /// Copy file on local + pub(crate) fn action_local_copy(&mut self, input: String) { + if let Some(idx) = self.get_local_file_idx() { + let dest_path: PathBuf = PathBuf::from(input); + let entry: FsEntry = self.local().get(idx).unwrap().clone(); + match self.host.copy(&entry, dest_path.as_path()) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Copied \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest_path.display() + ), + ); + // Reload entries + let wrkdir: PathBuf = self.local().wrkdir.clone(); + self.local_scan(wrkdir.as_path()); + } + Err(err) => self.log_and_alert( + LogLevel::Error, + format!( + "Could not copy \"{}\" to \"{}\": {}", + entry.get_abs_path().display(), + dest_path.display(), + err + ), + ), + } + } + } + + /// ### action_remote_copy + /// + /// Copy file on remote + pub(crate) fn action_remote_copy(&mut self, input: String) { + if let Some(idx) = self.get_remote_file_idx() { + let dest_path: PathBuf = PathBuf::from(input); + let entry: FsEntry = self.remote().get(idx).unwrap().clone(); + match self.client.as_mut().copy(&entry, dest_path.as_path()) { + Ok(_) => { + self.log( + LogLevel::Info, + format!( + "Copied \"{}\" to \"{}\"", + entry.get_abs_path().display(), + dest_path.display() + ), + ); + self.reload_remote_dir(); + } + Err(err) => self.log_and_alert( + LogLevel::Error, + format!( + "Could not copy \"{}\" to \"{}\": {}", + entry.get_abs_path().display(), + dest_path.display(), + err + ), + ), + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/delete.rs b/src/ui/activities/filetransfer_activity/actions/delete.rs new file mode 100644 index 0000000..4628c19 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/delete.rs @@ -0,0 +1,84 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_delete(&mut self) { + let entry: Option = self.get_local_file_entry().cloned(); + if let Some(entry) = entry { + let full_path: PathBuf = entry.get_abs_path(); + // Delete file or directory and report status as popup + match self.host.remove(&entry) { + Ok(_) => { + // Reload files + let p: PathBuf = self.local().wrkdir.clone(); + self.local_scan(p.as_path()); + // Log + self.log( + LogLevel::Info, + format!("Removed file \"{}\"", full_path.display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not delete file \"{}\": {}", full_path.display(), err), + ); + } + } + } + } + + pub(crate) fn action_remote_delete(&mut self) { + if let Some(idx) = self.get_remote_file_idx() { + // Check if file entry exists + let entry = self.remote().get(idx).cloned(); + if let Some(entry) = entry { + let full_path: PathBuf = entry.get_abs_path(); + // Delete file + match self.client.remove(&entry) { + Ok(_) => { + self.reload_remote_dir(); + self.log( + LogLevel::Info, + format!("Removed file \"{}\"", full_path.display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not delete file \"{}\": {}", full_path.display(), err), + ); + } + } + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/edit.rs b/src/ui/activities/filetransfer_activity/actions/edit.rs new file mode 100644 index 0000000..3cb1543 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/edit.rs @@ -0,0 +1,76 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_edit_local_file(&mut self) { + if self.get_local_file_entry().is_some() { + let fsentry: FsEntry = self.get_local_file_entry().unwrap().clone(); + // Check if file + if fsentry.is_file() { + self.log( + LogLevel::Info, + format!("Opening file \"{}\"...", fsentry.get_abs_path().display()), + ); + // Edit file + match self.edit_local_file(fsentry.get_abs_path().as_path()) { + Ok(_) => { + // Reload directory + let pwd: PathBuf = self.local().wrkdir.clone(); + self.local_scan(pwd.as_path()); + } + Err(err) => self.log_and_alert(LogLevel::Error, err), + } + } + } + } + + pub(crate) fn action_edit_remote_file(&mut self) { + if self.get_remote_file_entry().is_some() { + let fsentry: FsEntry = self.get_remote_file_entry().unwrap().clone(); + // Check if file + if let FsEntry::File(file) = fsentry.clone() { + self.log( + LogLevel::Info, + format!("Opening file \"{}\"...", fsentry.get_abs_path().display()), + ); + // Edit file + match self.edit_remote_file(&file) { + Ok(_) => { + // Reload directory + let pwd: PathBuf = self.remote().wrkdir.clone(); + self.remote_scan(pwd.as_path()); + } + Err(err) => self.log_and_alert(LogLevel::Error, err), + } + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/exec.rs b/src/ui/activities/filetransfer_activity/actions/exec.rs new file mode 100644 index 0000000..73ab3cb --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/exec.rs @@ -0,0 +1,67 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_exec(&mut self, input: String) { + match self.host.exec(input.as_str()) { + Ok(output) => { + // Reload files + self.log(LogLevel::Info, format!("\"{}\": {}", input, output)); + let wrkdir: PathBuf = self.local().wrkdir.clone(); + self.local_scan(wrkdir.as_path()); + } + Err(err) => { + // Report err + self.log_and_alert( + LogLevel::Error, + format!("Could not execute command \"{}\": {}", input, err), + ); + } + } + } + + pub(crate) fn action_remote_exec(&mut self, input: String) { + match self.client.as_mut().exec(input.as_str()) { + Ok(output) => { + // Reload files + self.log(LogLevel::Info, format!("\"{}\": {}", input, output)); + self.reload_remote_dir(); + } + Err(err) => { + // Report err + self.log_and_alert( + LogLevel::Error, + format!("Could not execute command \"{}\": {}", input, err), + ); + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/find.rs b/src/ui/activities/filetransfer_activity/actions/find.rs new file mode 100644 index 0000000..6268bfb --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/find.rs @@ -0,0 +1,146 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::super::browser::FileExplorerTab; +use super::{FileTransferActivity, FsEntry, LogLevel}; + +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_find(&mut self, input: String) -> Result, String> { + match self.host.find(input.as_str()) { + Ok(entries) => Ok(entries), + Err(err) => Err(format!("Could not search for files: {}", err)), + } + } + + pub(crate) fn action_remote_find(&mut self, input: String) -> Result, String> { + match self.client.as_mut().find(input.as_str()) { + Ok(entries) => Ok(entries), + Err(err) => Err(format!("Could not search for files: {}", err)), + } + } + + pub(crate) fn action_find_changedir(&mut self, idx: usize) { + // Match entry + if let Some(entry) = self.found().as_ref().unwrap().get(idx) { + // Get path: if a directory, use directory path; if it is a File, get parent path + let path: PathBuf = match entry { + FsEntry::Directory(dir) => dir.abs_path.clone(), + FsEntry::File(file) => match file.abs_path.parent() { + None => PathBuf::from("."), + Some(p) => p.to_path_buf(), + }, + }; + // Change directory + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + self.local_changedir(path.as_path(), true) + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + self.remote_changedir(path.as_path(), true) + } + } + } + } + + pub(crate) fn action_find_transfer(&mut self, idx: usize, name: Option) { + let entry: Option = self.found().as_ref().unwrap().get(idx).cloned(); + if let Some(entry) = entry { + // Download file + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + let wrkdir: PathBuf = self.remote().wrkdir.clone(); + self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), name); + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + let wrkdir: PathBuf = self.local().wrkdir.clone(); + self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), name); + } + } + } + } + + pub(crate) fn action_find_delete(&mut self, idx: usize) { + let entry: Option = self.found().as_ref().unwrap().get(idx).cloned(); + if let Some(entry) = entry { + // Download file + match self.browser.tab() { + FileExplorerTab::FindLocal | FileExplorerTab::Local => { + let full_path: PathBuf = entry.get_abs_path(); + // Delete file or directory and report status as popup + match self.host.remove(&entry) { + Ok(_) => { + // Reload files + let p: PathBuf = self.local().wrkdir.clone(); + self.local_scan(p.as_path()); + // Log + self.log( + LogLevel::Info, + format!("Removed file \"{}\"", full_path.display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Could not delete file \"{}\": {}", + full_path.display(), + err + ), + ); + } + } + } + FileExplorerTab::FindRemote | FileExplorerTab::Remote => { + let full_path: PathBuf = entry.get_abs_path(); + // Delete file + match self.client.remove(&entry) { + Ok(_) => { + self.reload_remote_dir(); + self.log( + LogLevel::Info, + format!("Removed file \"{}\"", full_path.display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Could not delete file \"{}\": {}", + full_path.display(), + err + ), + ); + } + } + } + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/mkdir.rs b/src/ui/activities/filetransfer_activity/actions/mkdir.rs new file mode 100644 index 0000000..3f78823 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/mkdir.rs @@ -0,0 +1,70 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_mkdir(&mut self, input: String) { + match self.host.mkdir(PathBuf::from(input.as_str()).as_path()) { + Ok(_) => { + // Reload files + self.log(LogLevel::Info, format!("Created directory \"{}\"", input)); + let wrkdir: PathBuf = self.local().wrkdir.clone(); + self.local_scan(wrkdir.as_path()); + } + Err(err) => { + // Report err + self.log_and_alert( + LogLevel::Error, + format!("Could not create directory \"{}\": {}", input, err), + ); + } + } + } + pub(crate) fn action_remote_mkdir(&mut self, input: String) { + match self + .client + .as_mut() + .mkdir(PathBuf::from(input.as_str()).as_path()) + { + Ok(_) => { + // Reload files + self.log(LogLevel::Info, format!("Created directory \"{}\"", input)); + self.reload_remote_dir(); + } + Err(err) => { + // Report err + self.log_and_alert( + LogLevel::Error, + format!("Could not create directory \"{}\": {}", input, err), + ); + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/mod.rs b/src/ui/activities/filetransfer_activity/actions/mod.rs new file mode 100644 index 0000000..1dda2ce --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/mod.rs @@ -0,0 +1,85 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +pub(self) use super::{FileTransferActivity, FsEntry, LogLevel}; +use tuirealm::{Payload, Value}; + +// actions +pub(crate) mod change_dir; +pub(crate) mod copy; +pub(crate) mod delete; +pub(crate) mod edit; +pub(crate) mod exec; +pub(crate) mod find; +pub(crate) mod mkdir; +pub(crate) mod newfile; +pub(crate) mod rename; +pub(crate) mod save; + +impl FileTransferActivity { + /// ### get_local_file_entry + /// + /// Get local file entry + pub(crate) fn get_local_file_entry(&self) -> Option<&FsEntry> { + match self.get_local_file_idx() { + None => None, + Some(idx) => self.local().get(idx), + } + } + + /// ### get_remote_file_entry + /// + /// Get remote file entry + pub(crate) fn get_remote_file_entry(&self) -> Option<&FsEntry> { + match self.get_remote_file_idx() { + None => None, + Some(idx) => self.remote().get(idx), + } + } + + // -- private + + /// ### get_local_file_idx + /// + /// Get index of selected file in the local tab + fn get_local_file_idx(&self) -> Option { + match self.view.get_state(super::COMPONENT_EXPLORER_LOCAL) { + Some(Payload::One(Value::Usize(idx))) => Some(idx), + _ => None, + } + } + + /// ### get_remote_file_idx + /// + /// Get index of selected file in the remote file + fn get_remote_file_idx(&self) -> Option { + match self.view.get_state(super::COMPONENT_EXPLORER_REMOTE) { + Some(Payload::One(Value::Usize(idx))) => Some(idx), + _ => None, + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/newfile.rs b/src/ui/activities/filetransfer_activity/actions/newfile.rs new file mode 100644 index 0000000..f09baa8 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/newfile.rs @@ -0,0 +1,130 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_newfile(&mut self, input: String) { + // Check if file exists + let mut file_exists: bool = false; + for file in self.local().iter_files_all() { + if input == file.get_name() { + file_exists = true; + } + } + if file_exists { + self.log_and_alert( + LogLevel::Warn, + format!("File \"{}\" already exists", input,), + ); + return; + } + // Create file + let file_path: PathBuf = PathBuf::from(input.as_str()); + if let Err(err) = self.host.open_file_write(file_path.as_path()) { + self.log_and_alert( + LogLevel::Error, + format!("Could not create file \"{}\": {}", file_path.display(), err), + ); + } else { + self.log( + LogLevel::Info, + format!("Created file \"{}\"", file_path.display()), + ); + } + // Reload files + let path: PathBuf = self.local().wrkdir.clone(); + self.local_scan(path.as_path()); + } + + pub(crate) fn action_remote_newfile(&mut self, input: String) { + // Check if file exists + let mut file_exists: bool = false; + for file in self.remote().iter_files_all() { + if input == file.get_name() { + file_exists = true; + } + } + if file_exists { + self.log_and_alert( + LogLevel::Warn, + format!("File \"{}\" already exists", input,), + ); + return; + } + // Get path on remote + let file_path: PathBuf = PathBuf::from(input.as_str()); + // Create file (on local) + match tempfile::NamedTempFile::new() { + Err(err) => self.log_and_alert( + LogLevel::Error, + format!("Could not create tempfile: {}", err), + ), + Ok(tfile) => { + // Stat tempfile + let local_file: FsEntry = match self.host.stat(tfile.path()) { + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not stat tempfile: {}", err), + ); + return; + } + Ok(f) => f, + }; + if let FsEntry::File(local_file) = local_file { + // Create file + match self.client.send_file(&local_file, file_path.as_path()) { + Err(err) => self.log_and_alert( + LogLevel::Error, + format!("Could not create file \"{}\": {}", file_path.display(), err), + ), + Ok(writer) => { + // Finalize write + if let Err(err) = self.client.on_sent(writer) { + self.log_and_alert( + LogLevel::Warn, + format!("Could not finalize file: {}", err), + ); + } else { + self.log( + LogLevel::Info, + format!("Created file \"{}\"", file_path.display()), + ); + } + // Reload files + let path: PathBuf = self.remote().wrkdir.clone(); + self.remote_scan(path.as_path()); + } + } + } + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/rename.rs b/src/ui/activities/filetransfer_activity/actions/rename.rs new file mode 100644 index 0000000..47a483a --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/rename.rs @@ -0,0 +1,102 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry, LogLevel}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_rename(&mut self, input: String) { + let entry: Option = self.get_local_file_entry().cloned(); + if let Some(entry) = entry { + let mut dst_path: PathBuf = PathBuf::from(input); + // Check if path is relative + if dst_path.as_path().is_relative() { + let mut wrkdir: PathBuf = self.local().wrkdir.clone(); + wrkdir.push(dst_path); + dst_path = wrkdir; + } + let full_path: PathBuf = entry.get_abs_path(); + // Rename file or directory and report status as popup + match self.host.rename(&entry, dst_path.as_path()) { + Ok(_) => { + // Reload files + let path: PathBuf = self.local().wrkdir.clone(); + self.local_scan(path.as_path()); + // Log + self.log( + LogLevel::Info, + format!( + "Renamed file \"{}\" to \"{}\"", + full_path.display(), + dst_path.display() + ), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not rename file \"{}\": {}", full_path.display(), err), + ); + } + } + } + } + + pub(crate) fn action_remote_rename(&mut self, input: String) { + if let Some(idx) = self.get_remote_file_idx() { + let entry = self.remote().get(idx).cloned(); + if let Some(entry) = entry { + let dst_path: PathBuf = PathBuf::from(input); + let full_path: PathBuf = entry.get_abs_path(); + // Rename file or directory and report status as popup + match self.client.as_mut().rename(&entry, dst_path.as_path()) { + Ok(_) => { + // Reload files + let path: PathBuf = self.remote().wrkdir.clone(); + self.remote_scan(path.as_path()); + // Log + self.log( + LogLevel::Info, + format!( + "Renamed file \"{}\" to \"{}\"", + full_path.display(), + dst_path.display() + ), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!("Could not rename file \"{}\": {}", full_path.display(), err), + ); + } + } + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/actions/save.rs b/src/ui/activities/filetransfer_activity/actions/save.rs new file mode 100644 index 0000000..22912a9 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/actions/save.rs @@ -0,0 +1,56 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// locals +use super::{FileTransferActivity, FsEntry}; +use std::path::PathBuf; + +impl FileTransferActivity { + pub(crate) fn action_local_saveas(&mut self, input: String) { + if let Some(idx) = self.get_local_file_idx() { + // Get pwd + let wrkdir: PathBuf = self.remote().wrkdir.clone(); + if self.local().get(idx).is_some() { + let file: FsEntry = self.local().get(idx).unwrap().clone(); + // Call upload; pass realfile, keep link name + self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(input)); + } + } + } + + pub(crate) fn action_remote_saveas(&mut self, input: String) { + if let Some(idx) = self.get_remote_file_idx() { + // Get pwd + let wrkdir: PathBuf = self.local().wrkdir.clone(); + if self.remote().get(idx).is_some() { + let file: FsEntry = self.remote().get(idx).unwrap().clone(); + // Call upload; pass realfile, keep link name + self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(input)); + } + } + } +} diff --git a/src/ui/activities/filetransfer_activity/browser.rs b/src/ui/activities/filetransfer_activity/browser.rs new file mode 100644 index 0000000..df64876 --- /dev/null +++ b/src/ui/activities/filetransfer_activity/browser.rs @@ -0,0 +1,177 @@ +//! ## FileTransferActivity +//! +//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall + +/** + * MIT License + * + * termscp - Copyright (c) 2021 Christian Visintin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +use crate::fs::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSorting, GroupDirs}; +use crate::fs::FsEntry; +use crate::system::config_client::ConfigClient; + +/// ## FileExplorerTab +/// +/// File explorer tab +#[derive(Clone, Copy)] +pub(super) enum FileExplorerTab { + Local, + Remote, + FindLocal, // Find result tab + FindRemote, // Find result tab +} + +/// ## Browser +/// +/// Browser contains the browser options +pub(super) struct Browser { + local: FileExplorer, // Local File explorer state + remote: FileExplorer, // Remote File explorer state + found: Option, // File explorer for find result + tab: FileExplorerTab, // Current selected tab + pub sync_browsing: bool, +} + +impl Browser { + /// ### new + /// + /// Build a new `Browser` struct + pub(super) fn new(cli: Option<&ConfigClient>) -> Self { + Self { + local: Self::build_local_explorer(cli), + remote: Self::build_remote_explorer(cli), + found: None, + tab: FileExplorerTab::Local, + sync_browsing: false, + } + } + + pub fn local(&self) -> &FileExplorer { + &self.local + } + + pub fn local_mut(&mut self) -> &mut FileExplorer { + &mut self.local + } + + pub fn remote(&self) -> &FileExplorer { + &self.remote + } + + pub fn remote_mut(&mut self) -> &mut FileExplorer { + &mut self.remote + } + + pub fn found(&self) -> Option<&FileExplorer> { + self.found.as_ref() + } + + pub fn found_mut(&mut self) -> Option<&mut FileExplorer> { + self.found.as_mut() + } + + pub fn set_found(&mut self, files: Vec) { + let mut explorer = Self::build_found_explorer(); + explorer.set_files(files); + self.found = Some(explorer); + } + + pub fn del_found(&mut self) { + self.found = None; + } + + pub(super) fn tab(&self) -> FileExplorerTab { + self.tab + } + + /// ### change_tab + /// + /// Update tab value + pub(super) fn change_tab(&mut self, tab: FileExplorerTab) { + self.tab = tab; + } + + /// ### toggle_sync_browsing + /// + /// Invert the current state for the sync browsing + pub fn toggle_sync_browsing(&mut self) { + self.sync_browsing = !self.sync_browsing; + } + + /// ### build_local_explorer + /// + /// Build a file explorer with local host setup + pub fn build_local_explorer(cli: Option<&ConfigClient>) -> FileExplorer { + let mut builder = Self::build_explorer(cli); + if let Some(cli) = cli { + builder.with_formatter(cli.get_local_file_fmt().as_deref()); + } + builder.build() + } + + /// ### build_remote_explorer + /// + /// Build a file explorer with remote host setup + pub fn build_remote_explorer(cli: Option<&ConfigClient>) -> FileExplorer { + let mut builder = Self::build_explorer(cli); + if let Some(cli) = cli { + builder.with_formatter(cli.get_remote_file_fmt().as_deref()); + } + builder.build() + } + + /// ### build_explorer + /// + /// Build explorer reading configuration from `ConfigClient` + fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorerBuilder { + let mut builder: FileExplorerBuilder = FileExplorerBuilder::new(); + // Set common keys + builder + .with_file_sorting(FileSorting::ByName) + .with_stack_size(16); + match &cli { + Some(cli) => { + builder // Build according to current configuration + .with_group_dirs(cli.get_group_dirs()) + .with_hidden_files(cli.get_show_hidden_files()); + } + None => { + builder // Build default + .with_group_dirs(Some(GroupDirs::First)); + } + }; + builder + } + + /// ### build_found_explorer + /// + /// Build explorer reading from `ConfigClient`, for found result (has some differences) + fn build_found_explorer() -> FileExplorer { + FileExplorerBuilder::new() + .with_file_sorting(FileSorting::ByName) + .with_group_dirs(Some(GroupDirs::First)) + .with_hidden_files(true) + .with_stack_size(0) + .with_formatter(Some("{NAME} {SYMLINK}")) + .build() + } +} diff --git a/src/ui/activities/filetransfer_activity/misc.rs b/src/ui/activities/filetransfer_activity/misc.rs index d30cb4e..ec174ac 100644 --- a/src/ui/activities/filetransfer_activity/misc.rs +++ b/src/ui/activities/filetransfer_activity/misc.rs @@ -23,22 +23,23 @@ */ // Locals use super::{ConfigClient, FileTransferActivity, LogLevel, LogRecord}; -use crate::fs::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSorting, GroupDirs}; use crate::system::environment; use crate::system::sshkey_storage::SshKeyStorage; // Ext use std::env; use std::path::{Path, PathBuf}; +const LOG_CAPACITY: usize = 256; + impl FileTransferActivity { /// ### log /// /// Add message to log events - pub(super) fn log(&mut self, level: LogLevel, msg: &str) { + pub(super) fn log(&mut self, level: LogLevel, msg: String) { // Create log record let record: LogRecord = LogRecord::new(level, msg); //Check if history overflows the size - if self.log_records.len() + 1 > self.log_size { + if self.log_records.len() + 1 > LOG_CAPACITY { self.log_records.pop_back(); // Start cleaning events from back } // Eventually push front the new record @@ -52,8 +53,8 @@ impl FileTransferActivity { /// /// Add message to log events and also display it as an alert pub(super) fn log_and_alert(&mut self, level: LogLevel, msg: String) { - self.log(level, msg.as_str()); self.mount_error(msg.as_str()); + self.log(level, msg); // Update log let msg = self.update_logbox(); self.update(msg); @@ -91,64 +92,6 @@ impl FileTransferActivity { } } - /// ### build_explorer - /// - /// Build explorer reading configuration from `ConfigClient` - fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorerBuilder { - let mut builder: FileExplorerBuilder = FileExplorerBuilder::new(); - // Set common keys - builder - .with_file_sorting(FileSorting::ByName) - .with_stack_size(16); - match &cli { - Some(cli) => { - builder // Build according to current configuration - .with_group_dirs(cli.get_group_dirs()) - .with_hidden_files(cli.get_show_hidden_files()); - } - None => { - builder // Build default - .with_group_dirs(Some(GroupDirs::First)); - } - }; - builder - } - - /// ### build_local_explorer - /// - /// Build a file explorer with local host setup - pub(super) fn build_local_explorer(cli: Option<&ConfigClient>) -> FileExplorer { - let mut builder = Self::build_explorer(cli); - if let Some(cli) = cli { - builder.with_formatter(cli.get_local_file_fmt().as_deref()); - } - builder.build() - } - - /// ### build_remote_explorer - /// - /// Build a file explorer with remote host setup - pub(super) fn build_remote_explorer(cli: Option<&ConfigClient>) -> FileExplorer { - let mut builder = Self::build_explorer(cli); - if let Some(cli) = cli { - builder.with_formatter(cli.get_remote_file_fmt().as_deref()); - } - builder.build() - } - - /// ### build_found_explorer - /// - /// Build explorer reading from `ConfigClient`, for found result (has some differences) - pub(super) fn build_found_explorer() -> FileExplorer { - FileExplorerBuilder::new() - .with_file_sorting(FileSorting::ByName) - .with_group_dirs(Some(GroupDirs::First)) - .with_hidden_files(true) - .with_stack_size(0) - .with_formatter(Some("{NAME} {SYMLINK}")) - .build() - } - /// ### setup_text_editor /// /// Set text editor to use @@ -182,7 +125,7 @@ impl FileTransferActivity { pub(super) fn local_to_abs_path(&self, path: &Path) -> PathBuf { match path.is_relative() { true => { - let mut d: PathBuf = self.local.wrkdir.clone(); + let mut d: PathBuf = self.local().wrkdir.clone(); d.push(path); d } @@ -196,7 +139,7 @@ impl FileTransferActivity { pub(super) fn remote_to_abs_path(&self, path: &Path) -> PathBuf { match path.is_relative() { true => { - let mut wrkdir: PathBuf = self.remote.wrkdir.clone(); + let mut wrkdir: PathBuf = self.remote().wrkdir.clone(); wrkdir.push(path); wrkdir } diff --git a/src/ui/activities/filetransfer_activity/mod.rs b/src/ui/activities/filetransfer_activity/mod.rs index c0dd44e..9b324e0 100644 --- a/src/ui/activities/filetransfer_activity/mod.rs +++ b/src/ui/activities/filetransfer_activity/mod.rs @@ -26,11 +26,12 @@ * SOFTWARE. */ // This module is split into files, cause it's just too big -mod actions; -mod misc; -mod session; -mod update; -mod view; +pub(self) mod actions; +pub(self) mod browser; +pub(self) mod misc; +pub(self) mod session; +pub(self) mod update; +pub(self) mod view; // Dependencies extern crate chrono; @@ -46,7 +47,9 @@ use crate::filetransfer::sftp_transfer::SftpFileTransfer; use crate::filetransfer::{FileTransfer, FileTransferProtocol}; use crate::fs::explorer::FileExplorer; use crate::fs::FsEntry; +use crate::host::Localhost; use crate::system::config_client::ConfigClient; +use browser::Browser; // Includes use chrono::{DateTime, Local}; @@ -59,7 +62,6 @@ use tuirealm::View; // -- Storage keys const STORAGE_EXPLORER_WIDTH: &str = "FILETRANSFER_EXPLORER_WIDTH"; -const STORAGE_LOGBOX_WIDTH: &str = "LOGBOX_WIDTH"; // -- components @@ -87,16 +89,6 @@ const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING"; const COMPONENT_SPAN_STATUS_BAR: &str = "STATUS_BAR"; const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO"; -/// ## FileExplorerTab -/// -/// File explorer tab -enum FileExplorerTab { - Local, - Remote, - FindLocal, // Find result tab - FindRemote, // Find result tab -} - /// ## LogLevel /// /// Log level type @@ -119,11 +111,11 @@ impl LogRecord { /// ### new /// /// Instantiates a new LogRecord - pub fn new(level: LogLevel, msg: &str) -> LogRecord { + pub fn new(level: LogLevel, msg: String) -> LogRecord { LogRecord { time: Local::now(), level, - msg: String::from(msg), + msg, } } } @@ -203,30 +195,6 @@ impl Default for TransferStates { } } -/// ## Browser -/// -/// Browser contains the browser options -struct Browser { - pub sync_browsing: bool, -} - -impl Default for Browser { - fn default() -> Self { - Self { - sync_browsing: false, - } - } -} - -impl Browser { - /// ### toggle_sync_browsing - /// - /// Invert the current state for the sync browsing - pub fn toggle_sync_browsing(&mut self) { - self.sync_browsing = !self.sync_browsing; - } -} - /// ## FileTransferActivity /// /// FileTransferActivity is the data holder for the file transfer activity @@ -234,28 +202,25 @@ pub struct FileTransferActivity { exit_reason: Option, // Exit reason context: Option, // Context holder view: View, // View + host: Localhost, // Localhost client: Box, // File transfer client - local: FileExplorer, // Local File explorer state - remote: FileExplorer, // Remote File explorer state - found: Option, // File explorer for find result - tab: FileExplorerTab, // Current selected tab + browser: Browser, // Browser log_records: VecDeque, // Log records - log_size: usize, // Log records size (max) transfer: TransferStates, // Transfer states - browser: Browser, // Browser states } impl FileTransferActivity { /// ### new /// /// Instantiates a new FileTransferActivity - pub fn new(protocol: FileTransferProtocol) -> FileTransferActivity { + pub fn new(host: Localhost, protocol: FileTransferProtocol) -> FileTransferActivity { // Get config client let config_client: Option = Self::init_config_client(); FileTransferActivity { exit_reason: None, context: None, view: View::init(), + host, client: match protocol { FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new( Self::make_ssh_storage(config_client.as_ref()), @@ -265,16 +230,35 @@ impl FileTransferActivity { Self::make_ssh_storage(config_client.as_ref()), )), }, - local: Self::build_local_explorer(config_client.as_ref()), - remote: Self::build_remote_explorer(config_client.as_ref()), - found: None, - tab: FileExplorerTab::Local, + browser: Browser::new(config_client.as_ref()), log_records: VecDeque::with_capacity(256), // 256 events is enough I guess - log_size: 256, // Must match with capacity transfer: TransferStates::default(), - browser: Browser::default(), } } + + pub(crate) fn local(&self) -> &FileExplorer { + self.browser.local() + } + + pub(crate) fn local_mut(&mut self) -> &mut FileExplorer { + self.browser.local_mut() + } + + pub(crate) fn remote(&self) -> &FileExplorer { + self.browser.remote() + } + + pub(crate) fn remote_mut(&mut self) -> &mut FileExplorer { + self.browser.remote_mut() + } + + pub(crate) fn found(&self) -> Option<&FileExplorer> { + self.browser.found() + } + + pub(crate) fn found_mut(&mut self) -> Option<&mut FileExplorer> { + self.browser.found_mut() + } } /** @@ -296,10 +280,10 @@ impl Activity for FileTransferActivity { // Put raw mode on enabled let _ = enable_raw_mode(); // Set working directory - let pwd: PathBuf = self.context.as_ref().unwrap().local.pwd(); + let pwd: PathBuf = self.host.pwd(); // Get files at current wd self.local_scan(pwd.as_path()); - self.local.wrkdir = pwd; + self.local_mut().wrkdir = pwd; // Configure text editor self.setup_text_editor(); // init view diff --git a/src/ui/activities/filetransfer_activity/session.rs b/src/ui/activities/filetransfer_activity/session.rs index a98f55b..d1c66d1 100644 --- a/src/ui/activities/filetransfer_activity/session.rs +++ b/src/ui/activities/filetransfer_activity/session.rs @@ -86,7 +86,7 @@ impl FileTransferActivity { // Log welcome self.log( LogLevel::Info, - format!("Established connection with '{}': \"{}\"", addr, banner).as_ref(), + format!("Established connection with '{}': \"{}\"", addr, banner), ); } // Try to change directory to entry directory @@ -141,7 +141,7 @@ impl FileTransferActivity { if let Ok(pwd) = self.client.pwd() { self.remote_scan(pwd.as_path()); // Set wrkdir - self.remote.wrkdir = pwd; + self.remote_mut().wrkdir = pwd; } } @@ -192,8 +192,7 @@ impl FileTransferActivity { "Could not remove created file {}: {}", remote_path.display(), err - ) - .as_str(), + ), ), Ok(entry) => { if let Err(err) = self.client.remove(&entry) { @@ -203,8 +202,7 @@ impl FileTransferActivity { "Could not remove created file {}: {}", remote_path.display(), err - ) - .as_str(), + ), ); } } @@ -218,16 +216,10 @@ impl FileTransferActivity { Ok(_) => { self.log( LogLevel::Info, - format!("Created directory \"{}\"", remote_path.display()).as_ref(), + format!("Created directory \"{}\"", remote_path.display()), ); // Get files in dir - match self - .context - .as_ref() - .unwrap() - .local - .scan_dir(dir.abs_path.as_path()) - { + match self.host.scan_dir(dir.abs_path.as_path()) { Ok(entries) => { // Iterate over files for entry in entries.iter() { @@ -265,7 +257,7 @@ impl FileTransferActivity { } } // Scan dir on remote - let path: PathBuf = self.remote.wrkdir.clone(); + let path: PathBuf = self.remote().wrkdir.clone(); self.remote_scan(path.as_path()); // If aborted; show popup if self.transfer.aborted { @@ -322,28 +314,25 @@ impl FileTransferActivity { err, TransferErrorReason::Abrupted | TransferErrorReason::LocalIoError(_) ) { - let local = &mut self.context.as_mut().unwrap().local; // Stat file - match local.stat(local_file_path.as_path()) { + match self.host.stat(local_file_path.as_path()) { Err(err) => self.log( LogLevel::Error, format!( "Could not remove created file {}: {}", local_file_path.display(), err - ) - .as_str(), + ), ), Ok(entry) => { - if let Err(err) = local.remove(&entry) { + if let Err(err) = self.host.remove(&entry) { self.log( LogLevel::Error, format!( "Could not remove created file {}: {}", local_file_path.display(), err - ) - .as_str(), + ), ); } } @@ -359,24 +348,12 @@ impl FileTransferActivity { None => local_dir_path.push(dir.name.as_str()), } // Create directory on local - match self - .context - .as_mut() - .unwrap() - .local - .mkdir_ex(local_dir_path.as_path(), true) - { + match self.host.mkdir_ex(local_dir_path.as_path(), true) { Ok(_) => { // Apply file mode to directory #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] if let Some(pex) = dir.unix_pex { - if let Err(err) = self - .context - .as_ref() - .unwrap() - .local - .chmod(local_dir_path.as_path(), pex) - { + if let Err(err) = self.host.chmod(local_dir_path.as_path(), pex) { self.log( LogLevel::Error, format!( @@ -384,14 +361,13 @@ impl FileTransferActivity { pex, local_dir_path.display(), err - ) - .as_ref(), + ), ); } } self.log( LogLevel::Info, - format!("Created directory \"{}\"", local_dir_path.display()).as_ref(), + format!("Created directory \"{}\"", local_dir_path.display()), ); // Get files in dir match self.client.list_dir(dir.abs_path.as_path()) { @@ -426,8 +402,7 @@ impl FileTransferActivity { "Failed to create directory \"{}\": {}", local_dir_path.display(), err - ) - .as_ref(), + ), ); } } @@ -464,13 +439,7 @@ impl FileTransferActivity { ) -> Result<(), TransferErrorReason> { // Upload file // Try to open local file - match self - .context - .as_ref() - .unwrap() - .local - .open_file_read(local.abs_path.as_path()) - { + match self.host.open_file_read(local.abs_path.as_path()) { Ok(mut fhnd) => match self.client.send_file(local, remote) { Ok(mut rhnd) => { // Write file @@ -544,7 +513,7 @@ impl FileTransferActivity { if let Err(err) = self.client.on_sent(rhnd) { self.log( LogLevel::Warn, - format!("Could not finalize remote stream: \"{}\"", err).as_str(), + format!("Could not finalize remote stream: \"{}\"", err), ); } // if upload was abrupted, return error @@ -559,8 +528,7 @@ impl FileTransferActivity { remote.display(), fmt_millis(self.transfer.started.elapsed()), ByteSize(self.transfer.bytes_per_second()), - ) - .as_ref(), + ), ); } Err(err) => return Err(TransferErrorReason::FileTransferError(err)), @@ -580,7 +548,7 @@ impl FileTransferActivity { file_name: String, ) -> Result<(), TransferErrorReason> { // Try to open local file - match self.context.as_ref().unwrap().local.open_file_write(local) { + match self.host.open_file_write(local) { Ok(mut local_file) => { // Download file from remote match self.client.recv_file(remote) { @@ -647,7 +615,7 @@ impl FileTransferActivity { if let Err(err) = self.client.on_recv(rhnd) { self.log( LogLevel::Warn, - format!("Could not finalize remote stream: \"{}\"", err).as_str(), + format!("Could not finalize remote stream: \"{}\"", err), ); } // If download was abrupted, return Error @@ -657,8 +625,7 @@ impl FileTransferActivity { // Apply file mode to file #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] if let Some(pex) = remote.unix_pex { - if let Err(err) = self.context.as_ref().unwrap().local.chmod(local, pex) - { + if let Err(err) = self.host.chmod(local, pex) { self.log( LogLevel::Error, format!( @@ -666,8 +633,7 @@ impl FileTransferActivity { pex, local.display(), err - ) - .as_ref(), + ), ); } } @@ -680,8 +646,7 @@ impl FileTransferActivity { local.display(), fmt_millis(self.transfer.started.elapsed()), ByteSize(self.transfer.bytes_per_second()), - ) - .as_ref(), + ), ); } Err(err) => return Err(TransferErrorReason::FileTransferError(err)), @@ -696,10 +661,10 @@ impl FileTransferActivity { /// /// Scan current local directory pub(super) fn local_scan(&mut self, path: &Path) { - match self.context.as_ref().unwrap().local.scan_dir(path) { + match self.host.scan_dir(path) { Ok(files) => { // Set files and sort (sorting is implicit) - self.local.set_files(files); + self.local_mut().set_files(files); } Err(err) => { self.log_and_alert( @@ -717,7 +682,7 @@ impl FileTransferActivity { match self.client.list_dir(path) { Ok(files) => { // Set files and sort (sorting is implicit) - self.remote.set_files(files); + self.remote_mut().set_files(files); } Err(err) => { self.log_and_alert( @@ -733,21 +698,21 @@ impl FileTransferActivity { /// Change directory for local pub(super) fn local_changedir(&mut self, path: &Path, push: bool) { // Get current directory - let prev_dir: PathBuf = self.local.wrkdir.clone(); + let prev_dir: PathBuf = self.local().wrkdir.clone(); // Change directory - match self.context.as_mut().unwrap().local.change_wrkdir(path) { + match self.host.change_wrkdir(path) { Ok(_) => { self.log( LogLevel::Info, - format!("Changed directory on local: {}", path.display()).as_str(), + format!("Changed directory on local: {}", path.display()), ); // Reload files self.local_scan(path); // Set wrkdir - self.local.wrkdir = PathBuf::from(path); + self.local_mut().wrkdir = PathBuf::from(path); // Push prev_dir to stack if push { - self.local.pushd(prev_dir.as_path()) + self.local_mut().pushd(prev_dir.as_path()) } } Err(err) => { @@ -762,21 +727,21 @@ impl FileTransferActivity { pub(super) fn remote_changedir(&mut self, path: &Path, push: bool) { // Get current directory - let prev_dir: PathBuf = self.remote.wrkdir.clone(); + let prev_dir: PathBuf = self.remote().wrkdir.clone(); // Change directory match self.client.as_mut().change_dir(path) { Ok(_) => { self.log( LogLevel::Info, - format!("Changed directory on remote: {}", path.display()).as_str(), + format!("Changed directory on remote: {}", path.display()), ); // Update files self.remote_scan(path); // Set wrkdir - self.remote.wrkdir = PathBuf::from(path); + self.remote_mut().wrkdir = PathBuf::from(path); // Push prev_dir to stack if push { - self.remote.pushd(prev_dir.as_path()) + self.remote_mut().pushd(prev_dir.as_path()) } } Err(err) => { @@ -826,8 +791,7 @@ impl FileTransferActivity { format!( "Changes performed through editor saved to \"{}\"!", path.display() - ) - .as_str(), + ), ), Err(err) => return Err(format!("Could not open editor: {}", err)), } @@ -858,8 +822,7 @@ impl FileTransferActivity { return Err(format!("Could not open file {}: {}", file.name, err)); } // Get current file modification time - let prev_mtime: SystemTime = match self.context.as_ref().unwrap().local.stat(tmpfile.path()) - { + let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) { Ok(e) => e.get_last_change_time(), Err(err) => { return Err(format!( @@ -874,8 +837,7 @@ impl FileTransferActivity { return Err(err); } // Get local fs entry - let tmpfile_entry: FsEntry = match self.context.as_ref().unwrap().local.stat(tmpfile.path()) - { + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { Ok(e) => e, Err(err) => { return Err(format!( @@ -893,21 +855,19 @@ impl FileTransferActivity { format!( "File \"{}\" has changed; writing changes to remote", file.abs_path.display() - ) - .as_ref(), + ), ); // Get local fs entry - let tmpfile_entry: FsEntry = - match self.context.as_ref().unwrap().local.stat(tmpfile.path()) { - Ok(e) => e, - Err(err) => { - return Err(format!( - "Could not stat \"{}\": {}", - tmpfile.path().display(), - err - )) - } - }; + let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) { + Ok(e) => e, + Err(err) => { + return Err(format!( + "Could not stat \"{}\": {}", + tmpfile.path().display(), + err + )) + } + }; // Write file let tmpfile_entry: &FsFile = match &tmpfile_entry { FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"), @@ -929,7 +889,7 @@ impl FileTransferActivity { false => { self.log( LogLevel::Info, - format!("File \"{}\" hasn't changed", file.abs_path.display()).as_ref(), + format!("File \"{}\" hasn't changed", file.abs_path.display()), ); } } diff --git a/src/ui/activities/filetransfer_activity/update.rs b/src/ui/activities/filetransfer_activity/update.rs index 66295de..2281599 100644 --- a/src/ui/activities/filetransfer_activity/update.rs +++ b/src/ui/activities/filetransfer_activity/update.rs @@ -29,7 +29,7 @@ extern crate bytesize; // locals use super::{ - FileExplorerTab, FileTransferActivity, LogLevel, COMPONENT_EXPLORER_FIND, + browser::FileExplorerTab, FileTransferActivity, LogLevel, COMPONENT_EXPLORER_FIND, COMPONENT_EXPLORER_LOCAL, COMPONENT_EXPLORER_REMOTE, COMPONENT_INPUT_COPY, COMPONENT_INPUT_EXEC, COMPONENT_INPUT_FIND, COMPONENT_INPUT_GOTO, COMPONENT_INPUT_MKDIR, COMPONENT_INPUT_NEWFILE, COMPONENT_INPUT_RENAME, COMPONENT_INPUT_SAVEAS, @@ -68,7 +68,7 @@ impl FileTransferActivity { (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_RIGHT) => { // Change tab self.view.active(COMPONENT_EXPLORER_REMOTE); - self.tab = FileExplorerTab::Remote; + self.browser.change_tab(FileExplorerTab::Remote); None } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_BACKSPACE) => { @@ -83,7 +83,7 @@ impl FileTransferActivity { (COMPONENT_EXPLORER_LOCAL, Msg::OnSubmit(Payload::One(Value::Usize(idx)))) => { // Match selected file let mut entry: Option = None; - if let Some(e) = self.local.get(*idx) { + if let Some(e) = self.local().get(*idx) { entry = Some(e.clone()); } if let Some(entry) = entry { @@ -102,7 +102,7 @@ impl FileTransferActivity { } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SPACE) => { // Get pwd - let wrkdir: PathBuf = self.remote.wrkdir.clone(); + let wrkdir: PathBuf = self.remote().wrkdir.clone(); // Get file and clone (due to mutable / immutable stuff...) if self.get_local_file_entry().is_some() { let file: FsEntry = self.get_local_file_entry().unwrap().clone(); @@ -116,7 +116,7 @@ impl FileTransferActivity { } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => { // Toggle hidden files - self.local.toggle_hidden_files(); + self.local_mut().toggle_hidden_files(); // Reload file list component self.update_local_filelist() } @@ -129,33 +129,13 @@ impl FileTransferActivity { } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_L) => { // Reload directory - let pwd: PathBuf = self.local.wrkdir.clone(); + let pwd: PathBuf = self.local().wrkdir.clone(); self.local_scan(pwd.as_path()); // Reload file list component self.update_local_filelist() } (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_O) => { - // Clone entry due to mutable stuff... - if self.get_local_file_entry().is_some() { - let fsentry: FsEntry = self.get_local_file_entry().unwrap().clone(); - // Check if file - if fsentry.is_file() { - self.log( - LogLevel::Info, - format!("Opening file \"{}\"...", fsentry.get_abs_path().display()) - .as_str(), - ); - // Edit file - match self.edit_local_file(fsentry.get_abs_path().as_path()) { - Ok(_) => { - // Reload directory - let pwd: PathBuf = self.local.wrkdir.clone(); - self.local_scan(pwd.as_path()); - } - Err(err) => self.log_and_alert(LogLevel::Error, err), - } - } - } + self.action_edit_local_file(); // Reload file list component self.update_local_filelist() } @@ -171,13 +151,13 @@ impl FileTransferActivity { (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_LEFT) => { // Change tab self.view.active(COMPONENT_EXPLORER_LOCAL); - self.tab = FileExplorerTab::Local; + self.browser.change_tab(FileExplorerTab::Local); None } (COMPONENT_EXPLORER_REMOTE, Msg::OnSubmit(Payload::One(Value::Usize(idx)))) => { // Match selected file let mut entry: Option = None; - if let Some(e) = self.remote.get(*idx) { + if let Some(e) = self.remote().get(*idx) { entry = Some(e.clone()); } if let Some(entry) = entry { @@ -200,7 +180,7 @@ impl FileTransferActivity { let file: FsEntry = self.get_remote_file_entry().unwrap().clone(); let name: String = file.get_name().to_string(); // Call upload; pass realfile, keep link name - let wrkdir: PathBuf = self.local.wrkdir.clone(); + let wrkdir: PathBuf = self.local().wrkdir.clone(); self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(name)); self.update_local_filelist() } else { @@ -219,7 +199,7 @@ impl FileTransferActivity { } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => { // Toggle hidden files - self.remote.toggle_hidden_files(); + self.remote_mut().toggle_hidden_files(); // Reload file list component self.update_remote_filelist() } @@ -232,33 +212,14 @@ impl FileTransferActivity { } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_L) => { // Reload directory - let pwd: PathBuf = self.remote.wrkdir.clone(); + let pwd: PathBuf = self.remote().wrkdir.clone(); self.remote_scan(pwd.as_path()); // Reload file list component self.update_remote_filelist() } (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_O) => { - // Clone entry due to mutable stuff... - if self.get_remote_file_entry().is_some() { - let fsentry: FsEntry = self.get_remote_file_entry().unwrap().clone(); - // Check if file - if let FsEntry::File(file) = fsentry.clone() { - self.log( - LogLevel::Info, - format!("Opening file \"{}\"...", fsentry.get_abs_path().display()) - .as_str(), - ); - // Edit file - match self.edit_remote_file(&file) { - Ok(_) => { - // Reload directory - let pwd: PathBuf = self.remote.wrkdir.clone(); - self.remote_scan(pwd.as_path()); - } - Err(err) => self.log_and_alert(LogLevel::Error, err), - } - } - } + // Edit file + self.action_edit_remote_file(); // Reload file list component self.update_remote_filelist() } @@ -371,7 +332,7 @@ impl FileTransferActivity { // Finalize find self.finalize_find(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -383,7 +344,7 @@ impl FileTransferActivity { Some(Payload::One(Value::Usize(idx))) => { self.action_find_transfer(idx, None); // Reload files - match self.tab { + match self.browser.tab() { // NOTE: swapped by purpose FileExplorerTab::FindLocal => self.update_remote_filelist(), FileExplorerTab::FindRemote => self.update_local_filelist(), @@ -411,14 +372,14 @@ impl FileTransferActivity { } (COMPONENT_INPUT_COPY, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { // Copy file - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_copy(input.to_string()), FileExplorerTab::Remote => self.action_remote_copy(input.to_string()), _ => panic!("Found tab doesn't support COPY"), } self.umount_copy(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -431,14 +392,14 @@ impl FileTransferActivity { } (COMPONENT_INPUT_EXEC, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { // Exex command - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_exec(input.to_string()), FileExplorerTab::Remote => self.action_remote_exec(input.to_string()), _ => panic!("Found tab doesn't support EXEC"), } self.umount_exec(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -452,7 +413,7 @@ impl FileTransferActivity { (COMPONENT_INPUT_FIND, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { self.umount_find_input(); // Find - let res: Result, String> = match self.tab { + let res: Result, String> = match self.browser.tab() { FileExplorerTab::Local => self.action_local_find(input.to_string()), FileExplorerTab::Remote => self.action_remote_find(input.to_string()), _ => panic!("Trying to search for files, while already in a find result"), @@ -465,18 +426,16 @@ impl FileTransferActivity { } Ok(files) => { // Create explorer and load files - let mut explorer = Self::build_found_explorer(); - explorer.set_files(files); - self.found = Some(explorer); + self.browser.set_found(files); // Mount result widget self.mount_find(input); self.update_find_list(); // Initialize tab - self.tab = match self.tab { + self.browser.change_tab(match self.browser.tab() { FileExplorerTab::Local => FileExplorerTab::FindLocal, FileExplorerTab::Remote => FileExplorerTab::FindRemote, _ => FileExplorerTab::FindLocal, - }; + }); } } None @@ -487,7 +446,7 @@ impl FileTransferActivity { None } (COMPONENT_INPUT_GOTO, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => { self.action_change_local_dir(input.to_string(), false) } @@ -500,14 +459,14 @@ impl FileTransferActivity { self.umount_goto(); // Reload files if sync if self.browser.sync_browsing { - match self.tab { + match self.browser.tab() { FileExplorerTab::Remote => self.update_local_filelist(), FileExplorerTab::Local => self.update_remote_filelist(), _ => None, }; } // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -519,14 +478,14 @@ impl FileTransferActivity { None } (COMPONENT_INPUT_MKDIR, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_mkdir(input.to_string()), FileExplorerTab::Remote => self.action_remote_mkdir(input.to_string()), _ => panic!("Found tab doesn't support MKDIR"), } self.umount_mkdir(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -538,14 +497,14 @@ impl FileTransferActivity { None } (COMPONENT_INPUT_NEWFILE, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_newfile(input.to_string()), FileExplorerTab::Remote => self.action_remote_newfile(input.to_string()), _ => panic!("Found tab doesn't support NEWFILE"), } self.umount_newfile(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -557,14 +516,14 @@ impl FileTransferActivity { None } (COMPONENT_INPUT_RENAME, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_rename(input.to_string()), FileExplorerTab::Remote => self.action_remote_rename(input.to_string()), _ => panic!("Found tab doesn't support RENAME"), } self.umount_rename(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -576,7 +535,7 @@ impl FileTransferActivity { None } (COMPONENT_INPUT_SAVEAS, Msg::OnSubmit(Payload::One(Value::Str(input)))) => { - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_saveas(input.to_string()), FileExplorerTab::Remote => self.action_remote_saveas(input.to_string()), FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { @@ -590,7 +549,7 @@ impl FileTransferActivity { } self.umount_saveas(); // Reload files - match self.tab { + match self.browser.tab() { // NOTE: Swapped is intentional FileExplorerTab::Local => self.update_remote_filelist(), FileExplorerTab::Remote => self.update_local_filelist(), @@ -612,7 +571,7 @@ impl FileTransferActivity { } (COMPONENT_RADIO_DELETE, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => { // Choice is 'YES' - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.action_local_delete(), FileExplorerTab::Remote => self.action_remote_delete(), FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => { @@ -622,14 +581,14 @@ impl FileTransferActivity { { self.action_find_delete(idx); // Reload entries - self.found.as_mut().unwrap().del_entry(idx); + self.found_mut().unwrap().del_entry(idx); self.update_find_list(); } } } self.umount_radio_delete(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), FileExplorerTab::FindLocal => self.update_local_filelist(), @@ -672,15 +631,15 @@ impl FileTransferActivity { 3 => FileSorting::BySize, _ => FileSorting::ByName, }; - match self.tab { - FileExplorerTab::Local => self.local.sort_by(sorting), - FileExplorerTab::Remote => self.remote.sort_by(sorting), + match self.browser.tab() { + FileExplorerTab::Local => self.local_mut().sort_by(sorting), + FileExplorerTab::Remote => self.remote_mut().sort_by(sorting), _ => panic!("Found result doesn't support SORTING"), } // Update status bar self.refresh_status_bar(); // Reload files - match self.tab { + match self.browser.tab() { FileExplorerTab::Local => self.update_local_filelist(), FileExplorerTab::Remote => self.update_remote_filelist(), _ => None, @@ -739,16 +698,16 @@ impl FileTransferActivity { "{}:{} ", hostname, FileTransferActivity::elide_wrkdir_path( - self.local.wrkdir.as_path(), + self.local().wrkdir.as_path(), hostname.as_str(), width ) .display() ); let files: Vec = self - .local + .local() .iter_files() - .map(|x: &FsEntry| self.local.fmt_file(x)) + .map(|x: &FsEntry| self.local().fmt_file(x)) .collect(); // Update let props = FileListPropsBuilder::from(props) @@ -780,16 +739,16 @@ impl FileTransferActivity { "{}:{} ", params.address, FileTransferActivity::elide_wrkdir_path( - self.remote.wrkdir.as_path(), + self.remote().wrkdir.as_path(), params.address.as_str(), width ) .display() ); let files: Vec = self - .remote + .remote() .iter_files() - .map(|x: &FsEntry| self.remote.fmt_file(x)) + .map(|x: &FsEntry| self.remote().fmt_file(x)) .collect(); // Update let props = FileListPropsBuilder::from(props) @@ -807,19 +766,9 @@ impl FileTransferActivity { pub(super) fn update_logbox(&mut self) -> Option<(String, Msg)> { match self.view.get_props(super::COMPONENT_LOG_BOX) { Some(props) => { - // Get width - let width: usize = self - .context - .as_ref() - .unwrap() - .store - .get_unsigned(super::STORAGE_LOGBOX_WIDTH) - .unwrap_or(256); // Make log entries let mut table: TableBuilder = TableBuilder::default(); for (idx, record) in self.log_records.iter().enumerate() { - // Split rows by width NOTE: -37 'cause log prefix -3 cause of log line cursor - let record_rows = textwrap::wrap(record.msg.as_str(), (width as usize) - 40); // Add row if not first row if idx > 0 { table.add_row(); @@ -829,42 +778,29 @@ impl FileTransferActivity { LogLevel::Warn => Color::Yellow, LogLevel::Info => Color::Green, }; - for (idx, row) in record_rows.iter().enumerate() { - match idx { - 0 => { - // First row - table - .add_col(TextSpan::from(format!( - "{}", - record.time.format("%Y-%m-%dT%H:%M:%S%Z") - ))) - .add_col(TextSpan::from(" [")) - .add_col( - TextSpanBuilder::new( - format!( - "{:5}", - match record.level { - LogLevel::Error => "ERROR", - LogLevel::Warn => "WARN", - LogLevel::Info => "INFO", - } - ) - .as_str(), - ) - .with_foreground(fg) - .build(), - ) - .add_col(TextSpan::from("]: ")) - .add_col(TextSpan::from(row.as_ref())); - } - _ => { - table.add_col(TextSpan::from(textwrap::indent( - row.as_ref(), - " ", - ))); - } - } - } + table + .add_col(TextSpan::from(format!( + "{}", + record.time.format("%Y-%m-%dT%H:%M:%S%Z") + ))) + .add_col(TextSpan::from(" [")) + .add_col( + TextSpanBuilder::new( + format!( + "{:5}", + match record.level { + LogLevel::Error => "ERROR", + LogLevel::Warn => "WARN", + LogLevel::Info => "INFO", + } + ) + .as_str(), + ) + .with_foreground(fg) + .build(), + ) + .add_col(TextSpan::from("]: ")) + .add_col(TextSpan::from(record.msg.as_ref())); } let table = table.build(); let props = LogboxPropsBuilder::from(props) @@ -911,13 +847,13 @@ impl FileTransferActivity { /// Finalize find process fn finalize_find(&mut self) { // Set found to none - self.found = None; + self.browser.del_found(); // Restore tab - self.tab = match self.tab { + self.browser.change_tab(match self.browser.tab() { FileExplorerTab::FindLocal => FileExplorerTab::Local, FileExplorerTab::FindRemote => FileExplorerTab::Remote, _ => FileExplorerTab::Local, - }; + }); } fn update_find_list(&mut self) -> Option<(String, Msg)> { @@ -927,11 +863,10 @@ impl FileTransferActivity { let title: String = props.texts.title.clone().unwrap_or_default(); // Prepare files let files: Vec = self - .found - .as_ref() + .found() .unwrap() .iter_files() - .map(|x: &FsEntry| self.found.as_ref().unwrap().fmt_file(x)) + .map(|x: &FsEntry| self.found().unwrap().fmt_file(x)) .collect(); let props = FileListPropsBuilder::from(props) .with_files(Some(title), files) diff --git a/src/ui/activities/filetransfer_activity/view.rs b/src/ui/activities/filetransfer_activity/view.rs index a0a321e..bee6c03 100644 --- a/src/ui/activities/filetransfer_activity/view.rs +++ b/src/ui/activities/filetransfer_activity/view.rs @@ -31,7 +31,7 @@ extern crate hostname; #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] extern crate users; // locals -use super::{Context, FileExplorerTab, FileTransferActivity}; +use super::{browser::FileExplorerTab, Context, FileTransferActivity}; use crate::fs::explorer::FileSorting; use crate::fs::FsEntry; use crate::ui::components::{ @@ -148,12 +148,9 @@ impl FileTransferActivity { if !store.isset(super::STORAGE_EXPLORER_WIDTH) { store.set_unsigned(super::STORAGE_EXPLORER_WIDTH, tabs_chunks[0].width as usize); } - if !store.isset(super::STORAGE_LOGBOX_WIDTH) { - store.set_unsigned(super::STORAGE_LOGBOX_WIDTH, chunks[1].width as usize); - } // Draw explorers // @! Local explorer (Find or default) - match self.tab { + match self.browser.tab() { FileExplorerTab::FindLocal => { self.view .render(super::COMPONENT_EXPLORER_FIND, f, tabs_chunks[0]) @@ -163,7 +160,7 @@ impl FileTransferActivity { .render(super::COMPONENT_EXPLORER_LOCAL, f, tabs_chunks[0]), } // @! Remote explorer (Find or default) - match self.tab { + match self.browser.tab() { FileExplorerTab::FindRemote => { self.view .render(super::COMPONENT_EXPLORER_FIND, f, tabs_chunks[1]) @@ -489,7 +486,7 @@ impl FileTransferActivity { pub(super) fn mount_find(&mut self, search: &str) { // Get color - let color: Color = match self.tab { + let color: Color = match self.browser.tab() { FileExplorerTab::Local | FileExplorerTab::FindLocal => Color::Yellow, FileExplorerTab::Remote | FileExplorerTab::FindRemote => Color::LightBlue, }; @@ -637,9 +634,9 @@ impl FileTransferActivity { } pub(super) fn mount_file_sorting(&mut self) { - let sorting: FileSorting = match self.tab { - FileExplorerTab::Local => self.local.get_file_sorting(), - FileExplorerTab::Remote => self.remote.get_file_sorting(), + let sorting: FileSorting = match self.browser.tab() { + FileExplorerTab::Local => self.local().get_file_sorting(), + FileExplorerTab::Remote => self.remote().get_file_sorting(), _ => panic!("You can't mount file sorting when in found result"), }; let index: usize = match sorting { @@ -824,14 +821,14 @@ impl FileTransferActivity { TextSpanBuilder::new(" Localhost file sorting: ") .with_foreground(Color::LightYellow) .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.local.get_file_sorting())) + TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting())) .with_foreground(Color::LightYellow) .reversed() .build(), TextSpanBuilder::new(" Remote host file sorting: ") .with_foreground(Color::LightBlue) .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.remote.get_file_sorting())) + TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) .with_foreground(Color::LightBlue) .reversed() .build(), diff --git a/src/ui/components/logbox.rs b/src/ui/components/logbox.rs index b6159e0..6237a7a 100644 --- a/src/ui/components/logbox.rs +++ b/src/ui/components/logbox.rs @@ -202,12 +202,13 @@ impl Component for LogBox { #[cfg(not(tarpaulin_include))] fn render(&self, render: &mut Canvas, area: Rect) { if self.props.visible { + let width: usize = area.width as usize - 4; // Make list let list_items: Vec = match self.props.texts.table.as_ref() { None => Vec::new(), Some(table) => table .iter() - .map(|row| ListItem::new(wrap_spans(row, area.width.into(), &self.props))) + .map(|row| ListItem::new(wrap_spans(row, width, &self.props))) .collect(), // Make List item from TextSpan }; let w = List::new(list_items) diff --git a/src/ui/context.rs b/src/ui/context.rs index 1199aed..a0fcfa7 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -33,7 +33,6 @@ extern crate tuirealm; use super::input::InputHandler; use super::store::Store; use crate::filetransfer::FileTransferProtocol; -use crate::host::Localhost; use crate::system::config_client::ConfigClient; // Includes @@ -49,7 +48,6 @@ use tuirealm::tui::Terminal; /// /// Context holds data structures used by the ui pub struct Context { - pub local: Localhost, pub ft_params: Option, pub(crate) config_client: Option, pub(crate) store: Store, @@ -74,16 +72,11 @@ impl Context { /// ### new /// /// Instantiates a new Context - pub fn new( - local: Localhost, - config_client: Option, - error: Option, - ) -> Context { + pub fn new(config_client: Option, error: Option) -> Context { // Create terminal let mut stdout = stdout(); assert!(execute!(stdout, EnterAlternateScreen).is_ok()); Context { - local, ft_params: None, config_client, store: Store::init(), @@ -93,14 +86,12 @@ impl Context { } } - /* NOTE: in case is necessary /// ### set_error /// /// Set context error pub fn set_error(&mut self, err: String) { self.error = Some(err); } - */ /// ### get_error /// @@ -165,7 +156,6 @@ mod tests { use super::*; use pretty_assertions::assert_eq; - use std::path::PathBuf; #[test] fn test_ui_context_ft_params() { @@ -181,16 +171,15 @@ mod tests { #[cfg(not(feature = "githubActions"))] fn test_ui_context() { // Prepare stuff - let wrkdir: PathBuf = std::env::current_dir().unwrap_or(PathBuf::from("/")); - let mut ctx: Context = Context::new( - Localhost::new(wrkdir).ok().unwrap(), - None, - Some(String::from("alles kaput")), - ); + let mut ctx: Context = Context::new(None, Some(String::from("alles kaput"))); assert!(ctx.error.is_some()); assert_eq!(ctx.get_error().unwrap().as_str(), "alles kaput"); assert!(ctx.error.is_none()); assert!(ctx.get_error().is_none()); + ctx.set_error(String::from("err")); + assert!(ctx.error.is_some()); + assert!(ctx.get_error().is_some()); + assert!(ctx.get_error().is_none()); // Try other methods ctx.enter_alternate_screen(); ctx.clear_screen();