From 1757eb5beced43b6d173f065bf1c4befb387dc55 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 10 Aug 2021 22:17:36 +0200 Subject: [PATCH 01/20] When uploading a directory, create directory only if it doesn't exist --- CHANGELOG.md | 10 ++++ src/ui/activities/filetransfer/session.rs | 69 +++++++++++------------ 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0322f00..0ffd096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [0.7.0](#070) - [0.6.0](#060) - [0.5.1](#051) - [0.5.0](#050) @@ -19,6 +20,15 @@ --- +## 0.7.0 + +Released on ?? + +> 🍁 Autumn update 2021 🍇 + +- Bugfix: + - Fixed [Issue 58](https://github.com/veeso/termscp/issues/58):When uploading a directory, create directory only if it doesn't exist + ## 0.6.0 Released on 23/07/2021 diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 9ea0b12..d4e6049 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -363,48 +363,47 @@ impl FileTransferActivity { } } FsEntry::Directory(dir) => { - // Create directory on remote - match self.client.mkdir(remote_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, - format!("Created directory \"{}\"", remote_path.display()), - ); - // Get files in dir - match self.host.scan_dir(dir.abs_path.as_path()) { - Ok(entries) => { - // Iterate over files - for entry in entries.iter() { - // If aborted; break - if self.transfer.aborted() { - break; - } - // Send entry; name is always None after first call - self.filetransfer_send_recurse( - &entry, - remote_path.as_path(), - None, - ); - } - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not scan directory \"{}\": {}", - dir.abs_path.display(), - err - ), - ); + // Check whether should create directory + if self.client.list_dir(remote_path.as_path()).is_err() { + match self.client.mkdir(remote_path.as_path()) { + Ok(_) => { + self.log( + LogLevel::Info, + format!("Created directory \"{}\"", remote_path.display()), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Failed to create directory \"{}\": {}", + remote_path.display(), + err + ), + ); + return; + } + } + } + // Get files in dir + match self.host.scan_dir(dir.abs_path.as_path()) { + Ok(entries) => { + // Iterate over files + for entry in entries.iter() { + // If aborted; break + if self.transfer.aborted() { + break; } + // Send entry; name is always None after first call + self.filetransfer_send_recurse(&entry, remote_path.as_path(), None); } } Err(err) => { self.log_and_alert( LogLevel::Error, format!( - "Failed to create directory \"{}\": {}", - remote_path.display(), + "Could not scan directory \"{}\": {}", + dir.abs_path.display(), err ), ); From 764cca73d1d06dd74b91b4195fc033c5ad7882d1 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 10 Aug 2021 22:27:20 +0200 Subject: [PATCH 02/20] linter --- CHANGELOG.md | 7 ++ src/activity_manager.rs | 2 +- src/config/serialization.rs | 29 ++++---- src/filetransfer/ftp_transfer.rs | 6 +- src/filetransfer/scp_transfer.rs | 4 +- src/filetransfer/sftp_transfer.rs | 4 +- src/fs/explorer/builder.rs | 6 +- src/fs/explorer/mod.rs | 66 +++++++++---------- src/system/bookmarks_client.rs | 10 +-- src/system/config_client.rs | 16 ++--- src/system/theme_provider.rs | 8 +-- src/ui/activities/auth/bookmarks.rs | 6 +- .../activities/filetransfer/actions/delete.rs | 4 +- src/ui/activities/filetransfer/lib/browser.rs | 4 +- src/ui/activities/filetransfer/mod.rs | 2 +- src/ui/activities/filetransfer/session.rs | 4 +- src/ui/activities/filetransfer/update.rs | 8 +-- src/ui/activities/filetransfer/view.rs | 16 ++--- src/ui/activities/setup/mod.rs | 2 +- 19 files changed, 104 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0322f00..1c1d072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [0.7.0](#070) - [0.6.0](#060) - [0.5.1](#051) - [0.5.0](#050) @@ -19,6 +20,12 @@ --- +## 0.7.0 + +Released on ?? + +> 🍁 Autumn update 🍇 + ## 0.6.0 Released on 23/07/2021 diff --git a/src/activity_manager.rs b/src/activity_manager.rs index 52132dc..4988524 100644 --- a/src/activity_manager.rs +++ b/src/activity_manager.rs @@ -188,7 +188,7 @@ impl ActivityManager { }; // If ft params is None, return None let ft_params: &FileTransferParams = match ctx.ft_params() { - Some(ft_params) => &ft_params, + Some(ft_params) => ft_params, None => { error!("Failed to start FileTransferActivity: file transfer params is None"); return None; diff --git a/src/config/serialization.rs b/src/config/serialization.rs index fa8db24..eacd88f 100644 --- a/src/config/serialization.rs +++ b/src/config/serialization.rs @@ -44,13 +44,13 @@ pub struct SerializerError { #[derive(Error, Debug)] pub enum SerializerErrorKind { #[error("Operation failed")] - GenericError, + Generic, #[error("IO error")] - IoError, + Io, #[error("Serialization error")] - SerializationError, + Serialization, #[error("Syntax error")] - SyntaxError, + Syntax, } impl SerializerError { @@ -92,7 +92,7 @@ where Ok(dt) => dt, Err(err) => { return Err(SerializerError::new_ex( - SerializerErrorKind::SerializationError, + SerializerErrorKind::Serialization, err.to_string(), )) } @@ -102,7 +102,7 @@ where match writable.write_all(data.as_bytes()) { Ok(_) => Ok(()), Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )), } @@ -119,7 +119,7 @@ where let mut data: String = String::new(); if let Err(err) = readable.read_to_string(&mut data) { return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )); } @@ -131,7 +131,7 @@ where Ok(deserialized) } Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::SyntaxError, + SerializerErrorKind::Syntax, err.to_string(), )), } @@ -154,11 +154,11 @@ mod tests { #[test] fn test_config_serialization_errors() { - let error: SerializerError = SerializerError::new(SerializerErrorKind::SyntaxError); + let error: SerializerError = SerializerError::new(SerializerErrorKind::Syntax); assert!(error.msg.is_none()); assert_eq!(format!("{}", error), String::from("Syntax error")); let error: SerializerError = - SerializerError::new_ex(SerializerErrorKind::SyntaxError, String::from("bad syntax")); + SerializerError::new_ex(SerializerErrorKind::Syntax, String::from("bad syntax")); assert!(error.msg.is_some()); assert_eq!( format!("{}", error), @@ -166,20 +166,17 @@ mod tests { ); // Fmt assert_eq!( - format!( - "{}", - SerializerError::new(SerializerErrorKind::GenericError) - ), + format!("{}", SerializerError::new(SerializerErrorKind::Generic)), String::from("Operation failed") ); assert_eq!( - format!("{}", SerializerError::new(SerializerErrorKind::IoError)), + format!("{}", SerializerError::new(SerializerErrorKind::Io)), String::from("IO error") ); assert_eq!( format!( "{}", - SerializerError::new(SerializerErrorKind::SerializationError) + SerializerError::new(SerializerErrorKind::Serialization) ), String::from("Serialization error") ); diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 20088c7..829da89 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -189,7 +189,7 @@ impl FtpFileTransfer { FsEntry::Directory(FsDirectory { name: p .file_name() - .unwrap_or(&std::ffi::OsStr::new("")) + .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_string_lossy() .to_string(), abs_path: p.clone(), @@ -206,7 +206,7 @@ impl FtpFileTransfer { false => FsEntry::File(FsFile { name: p .file_name() - .unwrap_or(&std::ffi::OsStr::new("")) + .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_string_lossy() .to_string(), abs_path: p.clone(), @@ -659,7 +659,7 @@ impl FileTransfer for FtpFileTransfer { // Remove recursively files debug!("Removing {} entries from directory...", files.len()); for file in files.iter() { - if let Err(err) = self.remove(&file) { + if let Err(err) = self.remove(file) { return Err(FileTransferError::new_ex( FileTransferErrorType::PexError, err.to_string(), diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index dbeeca1..5f37141 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -169,7 +169,7 @@ impl ScpFileTransfer { // Get symlink; PATH mustn't be equal to filename let symlink: Option> = match symlink_path { None => None, - Some(p) => match p.file_name().unwrap_or(&std::ffi::OsStr::new("")) + Some(p) => match p.file_name().unwrap_or_else(|| std::ffi::OsStr::new("")) == file_name.as_str() { // If name is equal, don't stat path; otherwise it would get stuck @@ -339,7 +339,7 @@ impl FileTransfer for ScpFileTransfer { // Try addresses for socket_addr in socket_addresses.iter() { debug!("Trying socket address {}", socket_addr); - match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) { + match TcpStream::connect_timeout(socket_addr, Duration::from_secs(30)) { Ok(stream) => { debug!("{} succeded", socket_addr); tcp = Some(stream); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 54dcf51..928dd3c 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -282,7 +282,7 @@ impl FileTransfer for SftpFileTransfer { // Try addresses for socket_addr in socket_addresses.iter() { debug!("Trying socket address {}", socket_addr); - match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) { + match TcpStream::connect_timeout(socket_addr, Duration::from_secs(30)) { Ok(stream) => { tcp = Some(stream); break; @@ -602,7 +602,7 @@ impl FileTransfer for SftpFileTransfer { // Get directory files let directory_content: Vec = self.list_dir(d.abs_path.as_path())?; for entry in directory_content.iter() { - if let Err(err) = self.remove(&entry) { + if let Err(err) = self.remove(entry) { return Err(err); } } diff --git a/src/fs/explorer/builder.rs b/src/fs/explorer/builder.rs index 9ba2dce..4bb2185 100644 --- a/src/fs/explorer/builder.rs +++ b/src/fs/explorer/builder.rs @@ -124,7 +124,7 @@ mod tests { let explorer: FileExplorer = FileExplorerBuilder::new().build(); // Verify assert!(!explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES)); - assert_eq!(explorer.file_sorting, FileSorting::ByName); // Default + assert_eq!(explorer.file_sorting, FileSorting::Name); // Default assert_eq!(explorer.group_dirs, None); assert_eq!(explorer.stack_size, 16); } @@ -132,7 +132,7 @@ mod tests { #[test] fn test_fs_explorer_builder_new_all() { let explorer: FileExplorer = FileExplorerBuilder::new() - .with_file_sorting(FileSorting::ByModifyTime) + .with_file_sorting(FileSorting::ModifyTime) .with_group_dirs(Some(GroupDirs::First)) .with_hidden_files(true) .with_stack_size(24) @@ -140,7 +140,7 @@ mod tests { .build(); // Verify assert!(explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES)); - assert_eq!(explorer.file_sorting, FileSorting::ByModifyTime); // Default + assert_eq!(explorer.file_sorting, FileSorting::ModifyTime); // Default assert_eq!(explorer.group_dirs, Some(GroupDirs::First)); assert_eq!(explorer.stack_size, 24); } diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 3d33421..27b74f8 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -52,10 +52,10 @@ bitflags! { /// FileSorting defines the criteria for sorting files #[derive(Copy, Clone, PartialEq, std::fmt::Debug)] pub enum FileSorting { - ByName, - ByModifyTime, - ByCreationTime, - BySize, + Name, + ModifyTime, + CreationTime, + Size, } /// ## GroupDirs @@ -87,7 +87,7 @@ impl Default for FileExplorer { wrkdir: PathBuf::from("/"), dirstack: VecDeque::with_capacity(16), stack_size: 16, - file_sorting: FileSorting::ByName, + file_sorting: FileSorting::Name, group_dirs: None, opts: ExplorerOpts::empty(), fmt: Formatter::default(), @@ -237,10 +237,10 @@ impl FileExplorer { fn sort(&mut self) { // Choose sorting method match &self.file_sorting { - FileSorting::ByName => self.sort_files_by_name(), - FileSorting::ByCreationTime => self.sort_files_by_creation_time(), - FileSorting::ByModifyTime => self.sort_files_by_mtime(), - FileSorting::BySize => self.sort_files_by_size(), + FileSorting::Name => self.sort_files_by_name(), + FileSorting::CreationTime => self.sort_files_by_creation_time(), + FileSorting::ModifyTime => self.sort_files_by_mtime(), + FileSorting::Size => self.sort_files_by_size(), } // Directories first (NOTE: MUST COME AFTER OTHER SORTING) // Group directories if necessary @@ -318,10 +318,10 @@ impl FileExplorer { impl ToString for FileSorting { fn to_string(&self) -> String { String::from(match self { - FileSorting::ByCreationTime => "by_creation_time", - FileSorting::ByModifyTime => "by_mtime", - FileSorting::ByName => "by_name", - FileSorting::BySize => "by_size", + FileSorting::CreationTime => "by_creation_time", + FileSorting::ModifyTime => "by_mtime", + FileSorting::Name => "by_name", + FileSorting::Size => "by_size", }) } } @@ -330,10 +330,10 @@ impl FromStr for FileSorting { type Err = (); fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { - "by_creation_time" => Ok(FileSorting::ByCreationTime), - "by_mtime" => Ok(FileSorting::ByModifyTime), - "by_name" => Ok(FileSorting::ByName), - "by_size" => Ok(FileSorting::BySize), + "by_creation_time" => Ok(FileSorting::CreationTime), + "by_mtime" => Ok(FileSorting::ModifyTime), + "by_name" => Ok(FileSorting::Name), + "by_size" => Ok(FileSorting::Size), _ => Err(()), } } @@ -380,8 +380,8 @@ mod tests { assert_eq!(explorer.wrkdir, PathBuf::from("/")); assert_eq!(explorer.stack_size, 16); assert_eq!(explorer.group_dirs, None); - assert_eq!(explorer.file_sorting, FileSorting::ByName); - assert_eq!(explorer.get_file_sorting(), FileSorting::ByName); + assert_eq!(explorer.file_sorting, FileSorting::Name); + assert_eq!(explorer.get_file_sorting(), FileSorting::Name); } #[test] @@ -459,7 +459,7 @@ mod tests { make_fs_entry("Cargo.lock", false), make_fs_entry("codecov.yml", false), ]); - explorer.sort_by(FileSorting::ByName); + explorer.sort_by(FileSorting::Name); // First entry should be "Cargo.lock" assert_eq!(explorer.files.get(0).unwrap().get_name(), "Cargo.lock"); // Last should be "src/" @@ -475,7 +475,7 @@ mod tests { let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false); // Create files (files are then sorted by name) explorer.set_files(vec![entry1, entry2]); - explorer.sort_by(FileSorting::ByModifyTime); + explorer.sort_by(FileSorting::ModifyTime); // First entry should be "CODE_OF_CONDUCT.md" assert_eq!( explorer.files.get(0).unwrap().get_name(), @@ -494,7 +494,7 @@ mod tests { let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false); // Create files (files are then sorted by name) explorer.set_files(vec![entry1, entry2]); - explorer.sort_by(FileSorting::ByCreationTime); + explorer.sort_by(FileSorting::CreationTime); // First entry should be "CODE_OF_CONDUCT.md" assert_eq!( explorer.files.get(0).unwrap().get_name(), @@ -513,7 +513,7 @@ mod tests { make_fs_entry("src/", true), make_fs_entry_with_size("CONTRIBUTING.md", false, 256), ]); - explorer.sort_by(FileSorting::BySize); + explorer.sort_by(FileSorting::Size); // Directory has size 4096 assert_eq!(explorer.files.get(0).unwrap().get_name(), "src/"); assert_eq!(explorer.files.get(1).unwrap().get_name(), "README.md"); @@ -536,7 +536,7 @@ mod tests { make_fs_entry("Cargo.lock", false), make_fs_entry("codecov.yml", false), ]); - explorer.sort_by(FileSorting::ByName); + explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::First)); // First entry should be "docs" assert_eq!(explorer.files.get(0).unwrap().get_name(), "docs/"); @@ -563,7 +563,7 @@ mod tests { make_fs_entry("Cargo.lock", false), make_fs_entry("codecov.yml", false), ]); - explorer.sort_by(FileSorting::ByName); + explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::Last)); // Last entry should be "src" assert_eq!(explorer.files.get(8).unwrap().get_name(), "docs/"); @@ -614,25 +614,25 @@ mod tests { #[test] fn test_fs_explorer_to_string_from_str_traits() { // File Sorting - assert_eq!(FileSorting::ByCreationTime.to_string(), "by_creation_time"); - assert_eq!(FileSorting::ByModifyTime.to_string(), "by_mtime"); - assert_eq!(FileSorting::ByName.to_string(), "by_name"); - assert_eq!(FileSorting::BySize.to_string(), "by_size"); + assert_eq!(FileSorting::CreationTime.to_string(), "by_creation_time"); + assert_eq!(FileSorting::ModifyTime.to_string(), "by_mtime"); + assert_eq!(FileSorting::Name.to_string(), "by_name"); + assert_eq!(FileSorting::Size.to_string(), "by_size"); assert_eq!( FileSorting::from_str("by_creation_time").ok().unwrap(), - FileSorting::ByCreationTime + FileSorting::CreationTime ); assert_eq!( FileSorting::from_str("by_mtime").ok().unwrap(), - FileSorting::ByModifyTime + FileSorting::ModifyTime ); assert_eq!( FileSorting::from_str("by_name").ok().unwrap(), - FileSorting::ByName + FileSorting::Name ); assert_eq!( FileSorting::from_str("by_size").ok().unwrap(), - FileSorting::BySize + FileSorting::Size ); assert!(FileSorting::from_str("omar").is_err()); // Group dirs diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index ed40f65..4fc4805 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -115,7 +115,7 @@ impl BookmarksClient { if let Err(e) = key_storage.set_key(service_id, key.as_str()) { error!("Failed to set new key into storage: {}", e); return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, format!("Could not write key to storage: {}", e), )); } @@ -125,7 +125,7 @@ impl BookmarksClient { _ => { error!("Failed to get key from storage: {}", e); return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, format!("Could not get key from storage: {}", e), )); } @@ -328,7 +328,7 @@ impl BookmarksClient { Err(err) => { error!("Failed to write bookmarks: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -358,7 +358,7 @@ impl BookmarksClient { Err(err) => { error!("Failed to read bookmarks: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -407,7 +407,7 @@ impl BookmarksClient { match crypto::aes128_b64_decrypt(self.key.as_str(), secret) { Ok(txt) => Ok(txt), Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::SyntaxError, + SerializerErrorKind::Syntax, err.to_string(), )), } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 12a77d3..2a484e2 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -76,7 +76,7 @@ impl ConfigClient { if let Err(err) = create_dir(ssh_key_dir) { error!("Failed to create SSH key dir: {}", err); return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, format!( "Could not create SSH key directory \"{}\": {}", ssh_key_dir.display(), @@ -252,7 +252,7 @@ impl ConfigClient { ) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be saved, since in degraded mode"), )); } @@ -291,7 +291,7 @@ impl ConfigClient { pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be saved, since in degraded mode"), )); } @@ -351,7 +351,7 @@ impl ConfigClient { pub fn write_config(&self) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be saved, since in degraded mode"), )); } @@ -366,7 +366,7 @@ impl ConfigClient { Err(err) => { error!("Failed to write configuration file: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -379,7 +379,7 @@ impl ConfigClient { pub fn read_config(&mut self) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be loaded, since in degraded mode"), )); } @@ -401,7 +401,7 @@ impl ConfigClient { Err(err) => { error!("Failed to read configuration: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -432,7 +432,7 @@ impl ConfigClient { /// Make serializer error from `std::io::Error` fn make_io_err(err: std::io::Error) -> Result<(), SerializerError> { Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } diff --git a/src/system/theme_provider.rs b/src/system/theme_provider.rs index d878eb4..643687b 100644 --- a/src/system/theme_provider.rs +++ b/src/system/theme_provider.rs @@ -116,7 +116,7 @@ impl ThemeProvider { warn!("Configuration won't be loaded, since degraded; reloading default..."); self.theme = Theme::default(); return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Can't access theme file"), )); } @@ -139,7 +139,7 @@ impl ThemeProvider { Err(err) => { error!("Failed to read theme: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -153,7 +153,7 @@ impl ThemeProvider { if self.degraded { warn!("Configuration won't be saved, since in degraded mode"); return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Can't access theme file"), )); } @@ -169,7 +169,7 @@ impl ThemeProvider { Err(err) => { error!("Failed to write theme: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } diff --git a/src/ui/activities/auth/bookmarks.rs b/src/ui/activities/auth/bookmarks.rs index 44c4499..d4115f1 100644 --- a/src/ui/activities/auth/bookmarks.rs +++ b/src/ui/activities/auth/bookmarks.rs @@ -44,7 +44,7 @@ impl AuthActivity { // Iterate over kyes let name: Option<&String> = self.bookmarks_list.get(idx); if let Some(name) = name { - bookmarks_cli.del_bookmark(&name); + bookmarks_cli.del_bookmark(name); // Write bookmarks self.write_bookmarks(); } @@ -60,7 +60,7 @@ impl AuthActivity { if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() { // Iterate over bookmarks if let Some(key) = self.bookmarks_list.get(idx) { - if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) { + if let Some(bookmark) = bookmarks_cli.get_bookmark(key) { // Load parameters into components self.load_bookmark_into_gui( bookmark.0, bookmark.1, bookmark.2, bookmark.3, bookmark.4, @@ -104,7 +104,7 @@ impl AuthActivity { if let Some(client) = self.bookmarks_client.as_mut() { let name: Option<&String> = self.recents_list.get(idx); if let Some(name) = name { - client.del_recent(&name); + client.del_recent(name); // Write bookmarks self.write_bookmarks(); } diff --git a/src/ui/activities/filetransfer/actions/delete.rs b/src/ui/activities/filetransfer/actions/delete.rs index 3d95a19..4c1bba5 100644 --- a/src/ui/activities/filetransfer/actions/delete.rs +++ b/src/ui/activities/filetransfer/actions/delete.rs @@ -72,7 +72,7 @@ impl FileTransferActivity { } pub(crate) fn local_remove_file(&mut self, entry: &FsEntry) { - match self.host.remove(&entry) { + match self.host.remove(entry) { Ok(_) => { // Log self.log( @@ -94,7 +94,7 @@ impl FileTransferActivity { } pub(crate) fn remote_remove_file(&mut self, entry: &FsEntry) { - match self.client.remove(&entry) { + match self.client.remove(entry) { Ok(_) => { self.log( LogLevel::Info, diff --git a/src/ui/activities/filetransfer/lib/browser.rs b/src/ui/activities/filetransfer/lib/browser.rs index df49198..501ab92 100644 --- a/src/ui/activities/filetransfer/lib/browser.rs +++ b/src/ui/activities/filetransfer/lib/browser.rs @@ -142,7 +142,7 @@ impl Browser { let mut builder: FileExplorerBuilder = FileExplorerBuilder::new(); // Set common keys builder - .with_file_sorting(FileSorting::ByName) + .with_file_sorting(FileSorting::Name) .with_stack_size(16) .with_group_dirs(cli.get_group_dirs()) .with_hidden_files(cli.get_show_hidden_files()); @@ -154,7 +154,7 @@ impl Browser { /// Build explorer reading from `ConfigClient`, for found result (has some differences) fn build_found_explorer() -> FileExplorer { FileExplorerBuilder::new() - .with_file_sorting(FileSorting::ByName) + .with_file_sorting(FileSorting::Name) .with_group_dirs(Some(GroupDirs::First)) .with_hidden_files(true) .with_stack_size(0) diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 91da013..3d3a655 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -228,7 +228,7 @@ impl FileTransferActivity { /// /// Returns config client reference fn config(&self) -> &ConfigClient { - &self.context().config() + self.context().config() } /// ### theme diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 9ea0b12..bb1ee06 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -381,7 +381,7 @@ impl FileTransferActivity { } // Send entry; name is always None after first call self.filetransfer_send_recurse( - &entry, + entry, remote_path.as_path(), None, ); @@ -730,7 +730,7 @@ impl FileTransferActivity { // Receive entry; name is always None after first call // Local path becomes local_dir_path self.filetransfer_recv_recurse( - &entry, + entry, local_dir_path.as_path(), None, ); diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 723a0ff..fb6e828 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -663,10 +663,10 @@ impl Update for FileTransferActivity { (COMPONENT_RADIO_SORTING, Msg::OnChange(Payload::One(Value::Usize(mode)))) => { // Get sorting mode let sorting: FileSorting = match mode { - 1 => FileSorting::ByModifyTime, - 2 => FileSorting::ByCreationTime, - 3 => FileSorting::BySize, - _ => FileSorting::ByName, + 1 => FileSorting::ModifyTime, + 2 => FileSorting::CreationTime, + 3 => FileSorting::Size, + _ => FileSorting::Name, }; match self.browser.tab() { FileExplorerTab::Local => self.local_mut().sort_by(sorting), diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index e9046a5..c0685a5 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -770,10 +770,10 @@ impl FileTransferActivity { _ => panic!("You can't mount file sorting when in found result"), }; let index: usize = match sorting { - FileSorting::ByCreationTime => 2, - FileSorting::ByModifyTime => 1, - FileSorting::ByName => 0, - FileSorting::BySize => 3, + FileSorting::CreationTime => 2, + FileSorting::ModifyTime => 1, + FileSorting::Name => 0, + FileSorting::Size => 3, }; self.view.mount( super::COMPONENT_RADIO_SORTING, @@ -1280,10 +1280,10 @@ impl FileTransferActivity { fn get_file_sorting_str(mode: FileSorting) -> &'static str { match mode { - FileSorting::ByName => "By name", - FileSorting::ByCreationTime => "By creation time", - FileSorting::ByModifyTime => "By modify time", - FileSorting::BySize => "By size", + FileSorting::Name => "By name", + FileSorting::CreationTime => "By creation time", + FileSorting::ModifyTime => "By modify time", + FileSorting::Size => "By size", } } diff --git a/src/ui/activities/setup/mod.rs b/src/ui/activities/setup/mod.rs index 1a07b39..d615189 100644 --- a/src/ui/activities/setup/mod.rs +++ b/src/ui/activities/setup/mod.rs @@ -152,7 +152,7 @@ impl SetupActivity { } fn config(&self) -> &ConfigClient { - &self.context().config() + self.context().config() } fn config_mut(&mut self) -> &mut ConfigClient { From 1558f4ffe430f383871f3ac2c98b7a7efd169282 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 17 Aug 2021 11:03:35 +0200 Subject: [PATCH 03/20] init 0.6.1 --- CHANGELOG.md | 6 ++---- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- dist/pkgs/freebsd/manifest | 2 +- install.sh | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1d072..dcb7c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog - [Changelog](#changelog) - - [0.7.0](#070) + - [0.6.1](#061) - [0.6.0](#060) - [0.5.1](#051) - [0.5.0](#050) @@ -20,11 +20,9 @@ --- -## 0.7.0 +## 0.6.1 -Released on ?? -> 🍁 Autumn update 🍇 ## 0.6.0 diff --git a/Cargo.lock b/Cargo.lock index 3dab2b9..05279d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1354,7 +1354,7 @@ dependencies = [ [[package]] name = "termscp" -version = "0.6.0" +version = "0.6.1" dependencies = [ "argh", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index de7ea32..d0390b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "termscp" readme = "README.md" repository = "https://github.com/veeso/termscp" -version = "0.6.0" +version = "0.6.1" [package.metadata.rpm] package = "termscp" diff --git a/README.md b/README.md index f69dcf6..4fd6cde 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@

Developed by @veeso

-

Current version: 0.6.0 (23/07/2021)

+

Current version: 0.6.1 (FIXME: 23/07/2021)

-[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.6.0-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) +[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.6.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) [![Linux](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![MacOs](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Windows](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![FreeBSD](https://github.com/veeso/termscp/workflows/FreeBSD/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp) diff --git a/dist/pkgs/freebsd/manifest b/dist/pkgs/freebsd/manifest index a63fcb2..c4b0d18 100644 --- a/dist/pkgs/freebsd/manifest +++ b/dist/pkgs/freebsd/manifest @@ -1,5 +1,5 @@ name: "termscp" -version: 0.5.1 +version: 0.6.1 origin: veeso/termscp comment: "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP" desc: < Date: Tue, 17 Aug 2021 12:38:24 +0200 Subject: [PATCH 04/20] Updated dependencies; migrated tui-realm to 0.6.0 --- CHANGELOG.md | 10 + Cargo.lock | 176 ++++++---- Cargo.toml | 11 +- src/lib.rs | 1 + src/ui/activities/auth/bookmarks.rs | 2 +- src/ui/activities/auth/update.rs | 114 +++--- src/ui/activities/auth/view.rs | 174 +++------ src/ui/activities/filetransfer/update.rs | 236 +++++++------ src/ui/activities/filetransfer/view.rs | 429 +++++++---------------- src/ui/activities/setup/update.rs | 212 +++++------ src/ui/activities/setup/view/mod.rs | 146 ++++---- src/ui/activities/setup/view/setup.rs | 89 ++--- src/ui/activities/setup/view/ssh_keys.rs | 56 +-- src/ui/activities/setup/view/theme.rs | 46 +-- src/ui/components/bookmark_list.rs | 83 +++-- src/ui/components/color_picker.rs | 13 +- src/ui/components/file_list.rs | 121 ++++--- src/ui/components/logbox.rs | 59 ++-- src/ui/components/mod.rs | 1 - src/ui/components/msgbox.rs | 268 -------------- src/utils/fmt.rs | 29 -- 21 files changed, 863 insertions(+), 1413 deletions(-) delete mode 100644 src/ui/components/msgbox.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb7c20..8528caf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,17 @@ ## 0.6.1 +Released on ?? +- Enhancements: + - Now that tui-rs supports title alignment, UI has been improved +- Dependencies: + - Updated `bitflags` to `1.3.2` + - Updated `bytesize` to `1.1.0` + - Updated `crossterm` to `0.20` + - Updated `open` to `2.0.1` + - Added `tui-realm-stdlib 0.6.0` + - Updated `tui-realm` to `0.6.0` ## 0.6.0 diff --git a/Cargo.lock b/Cargo.lock index 05279d4..1517e69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -138,9 +138,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytesize" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a18687293a1546b67c246452202bbbf143d239cb43494cc163da14979082da" +checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" [[package]] name = "cassowary" @@ -150,9 +150,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" @@ -264,34 +264,34 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" dependencies = [ "bitflags", "crossterm_winapi", - "lazy_static", "libc", "mio", "parking_lot 0.11.1", "signal-hook", + "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" dependencies = [ "winapi", ] [[package]] name = "crypto-mac" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" dependencies = [ "generic-array", "subtle", @@ -506,9 +506,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" dependencies = [ "cfg-if 1.0.0", ] @@ -521,9 +521,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.51" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" dependencies = [ "wasm-bindgen", ] @@ -548,9 +548,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.98" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" [[package]] name = "libssh2-sys" @@ -630,9 +630,9 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "md-5" @@ -675,9 +675,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", @@ -790,11 +790,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "open" -version = "1.7.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1711eb4b31ce4ad35b0f316d8dfba4fe5c7ad601c448446d84aae7a896627b20" +checksum = "b46b233de7d83bc167fe43ae2dda3b5b84e80e09cceba581e4decb958a4896bf" dependencies = [ - "which", + "pathdiff", "winapi", ] @@ -884,7 +884,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "smallvec", "winapi", ] @@ -895,6 +895,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cacbb3c4ff353b534a67fb8d7524d00229da4cb1dc8c79f4db96e375ab5b619" +[[package]] +name = "pathdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -927,9 +933,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ "unicode-xid", ] @@ -1032,9 +1038,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -1046,7 +1052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.3", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", ] [[package]] @@ -1209,18 +1215,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" dependencies = [ "proc-macro2", "quote", @@ -1229,9 +1235,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" dependencies = [ "itoa", "ryu", @@ -1253,13 +1259,23 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.1.17" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" dependencies = [ "libc", "mio", - "signal-hook-registry", + "signal-hook", ] [[package]] @@ -1314,15 +1330,15 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", @@ -1338,7 +1354,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "rand 0.8.4", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "remove_dir_all", "winapi", ] @@ -1383,6 +1399,7 @@ dependencies = [ "textwrap", "thiserror", "toml", + "tui-realm-stdlib", "tuirealm", "ureq", "users", @@ -1445,9 +1462,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" dependencies = [ "tinyvec_macros", ] @@ -1469,9 +1486,9 @@ dependencies = [ [[package]] name = "tui" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "861d8f3ad314ede6219bcb2ab844054b1de279ee37a9bc38e3d606f9d3fb2a71" +checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" dependencies = [ "bitflags", "cassowary", @@ -1481,15 +1498,24 @@ dependencies = [ ] [[package]] -name = "tuirealm" -version = "0.4.3" +name = "tui-realm-stdlib" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcbd06f2aa6a2424aaa245c10e8767fe3f0fee234ac8c144cb15eaf2ee37ce9" +checksum = "6bff91e1cdc741a7487d8cb20ac038e5ba926a0ec97b0f2ea918ac75640b9da5" +dependencies = [ + "textwrap", + "tuirealm", + "unicode-width", +] + +[[package]] +name = "tuirealm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634ad8e6a4b80ef032d31356b55964a995da5d05a9cf3a1bd134bae1ba7c197a" dependencies = [ "crossterm", - "textwrap", "tui", - "unicode-width", ] [[package]] @@ -1500,18 +1526,15 @@ checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" [[package]] name = "unicode-linebreak" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a31f45d18a3213b918019f78fe6a73a14ab896807f0aaf5622aa0684749455" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" dependencies = [ "regex", ] @@ -1615,9 +1638,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1625,9 +1648,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" dependencies = [ "bumpalo", "lazy_static", @@ -1640,9 +1663,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1650,9 +1673,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" dependencies = [ "proc-macro2", "quote", @@ -1663,15 +1686,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" +checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" [[package]] name = "web-sys" -version = "0.3.51" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" dependencies = [ "js-sys", "wasm-bindgen", @@ -1698,11 +1721,12 @@ dependencies = [ [[package]] name = "which" -version = "4.1.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" dependencies = [ "either", + "lazy_static", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index d0390b6..7216e80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,11 @@ path = "src/main.rs" [dependencies] argh = "0.1.5" -bitflags = "1.2.1" -bytesize = "1.0.1" +bitflags = "1.3.2" +bytesize = "1.1.0" chrono = "0.4.19" content_inspector = "0.2.4" -crossterm = "0.19.0" +crossterm = "0.20" dirs = "3.0.1" edit = "0.1.3" ftp4 = { version = "4.0.2", features = [ "secure" ] } @@ -41,7 +41,7 @@ keyring = { version = "0.10.1", optional = true } lazy_static = "1.4.0" log = "0.4.14" magic-crypt = "3.1.7" -open = "1.7.0" +open = "2.0.1" rand = "0.8.4" regex = "1.5.4" rpassword = "5.0.1" @@ -52,7 +52,8 @@ tempfile = "3.1.0" textwrap = "0.14.2" thiserror = "^1.0.0" toml = "0.5.8" -tuirealm = { version = "0.4.3", features = [ "with-components" ] } +tui-realm-stdlib = "0.6.0" +tuirealm = "0.6.0" ureq = { version = "2.1.0", features = [ "json" ] } whoami = "1.1.1" wildmatch = "2.0.0" diff --git a/src/lib.rs b/src/lib.rs index b6e3d0e..48bed11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,7 @@ extern crate regex; extern crate ssh2; extern crate tempfile; extern crate textwrap; +extern crate tui_realm_stdlib; extern crate tuirealm; extern crate ureq; #[cfg(target_family = "unix")] diff --git a/src/ui/activities/auth/bookmarks.rs b/src/ui/activities/auth/bookmarks.rs index d4115f1..dcd9946 100644 --- a/src/ui/activities/auth/bookmarks.rs +++ b/src/ui/activities/auth/bookmarks.rs @@ -32,7 +32,7 @@ use crate::system::environment; // Ext use std::path::PathBuf; -use tuirealm::components::{input::InputPropsBuilder, radio::RadioPropsBuilder}; +use tui_realm_stdlib::{input::InputPropsBuilder, radio::RadioPropsBuilder}; use tuirealm::{Payload, PropsBuilder, Value}; impl AuthActivity { diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index 9c6822a..dd2275d 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -35,7 +35,7 @@ use super::{ COMPONENT_TEXT_HELP, COMPONENT_TEXT_NEW_VERSION_NOTES, COMPONENT_TEXT_SIZE_ERR, }; use crate::ui::keymap::*; -use tuirealm::components::InputPropsBuilder; +use tui_realm_stdlib::InputPropsBuilder; use tuirealm::{Msg, Payload, PropsBuilder, Update, Value}; // -- update @@ -52,53 +52,53 @@ impl Update for AuthActivity { None => None, // Exit after None Some(msg) => match msg { // Focus ( DOWN ) - (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_ADDR); None } - (COMPONENT_INPUT_ADDR, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_PORT); None } - (COMPONENT_INPUT_PORT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_USERNAME); None } - (COMPONENT_INPUT_USERNAME, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_PASSWORD); None } - (COMPONENT_INPUT_PASSWORD, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_RADIO_PROTOCOL); None } // Focus ( UP ) - (COMPONENT_INPUT_PASSWORD, &MSG_KEY_UP) => { + (COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_USERNAME); None } - (COMPONENT_INPUT_USERNAME, &MSG_KEY_UP) => { + (COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_PORT); None } - (COMPONENT_INPUT_PORT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_ADDR); None } - (COMPONENT_INPUT_ADDR, &MSG_KEY_UP) => { + (COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_RADIO_PROTOCOL); None } - (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_UP) => { + (COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_PASSWORD); None @@ -118,25 +118,25 @@ impl Update for AuthActivity { } // Bookmarks commands // / - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_RIGHT) => { + (COMPONENT_BOOKMARKS_LIST, key) if key == &MSG_KEY_RIGHT => { // Give focus to recents self.view.active(COMPONENT_RECENTS_LIST); None } - (COMPONENT_RECENTS_LIST, &MSG_KEY_LEFT) => { + (COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_LEFT => { // Give focus to bookmarks self.view.active(COMPONENT_BOOKMARKS_LIST); None } // - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_DEL) - | (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_CHAR_E) => { + (COMPONENT_BOOKMARKS_LIST, key) + if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E => + { // Show delete popup self.mount_bookmark_del_dialog(); None } - (COMPONENT_RECENTS_LIST, &MSG_KEY_DEL) - | (COMPONENT_RECENTS_LIST, &MSG_KEY_CHAR_E) => { + (COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E => { // Show delete popup self.mount_recent_del_dialog(); None @@ -203,67 +203,68 @@ impl Update for AuthActivity { } } // hide tab - (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, &MSG_KEY_ESC) => { + (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, key) if key == &MSG_KEY_ESC => { self.umount_recent_del_dialog(); None } (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, _) => None, - (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, &MSG_KEY_ESC) => { + (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, key) if key == &MSG_KEY_ESC => { self.umount_bookmark_del_dialog(); None } (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, _) => None, // Error message - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => { // Umount text error self.umount_error(); None } (COMPONENT_TEXT_ERROR, _) => None, - (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ESC) - | (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_NEW_VERSION_NOTES, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount release notes self.umount_release_notes(); None } (COMPONENT_TEXT_NEW_VERSION_NOTES, _) => None, // Help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } // Release notes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Show release notes self.mount_release_notes(); None } - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => { // Hide text help self.umount_help(); None } (COMPONENT_TEXT_HELP, _) => None, // Enter setup - (_, &MSG_KEY_CTRL_C) => { + (_, key) if key == &MSG_KEY_CTRL_C => { self.exit_reason = Some(super::ExitReason::EnterSetup); None } // Save bookmark; show popup - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show popup self.mount_bookmark_save_dialog(); // Give focus to bookmark name self.view.active(COMPONENT_INPUT_BOOKMARK_NAME); None } - (COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_BOOKMARK_NAME, key) if key == &MSG_KEY_DOWN => { // Give focus to pwd self.view.active(COMPONENT_RADIO_BOOKMARK_SAVE_PWD); None } - (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_UP) => { + (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key) if key == &MSG_KEY_UP => { // Give focus to pwd self.view.active(COMPONENT_INPUT_BOOKMARK_NAME); None @@ -291,8 +292,9 @@ impl Update for AuthActivity { self.view_bookmarks() } // Hide save bookmark - (COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_ESC) - | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_BOOKMARK_NAME, key) | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key) + if key == &MSG_KEY_ESC => + { // Umount popup self.umount_bookmark_save_dialog(); None @@ -307,45 +309,30 @@ impl Update for AuthActivity { self.umount_quit(); None } - (COMPONENT_RADIO_QUIT, &MSG_KEY_ESC) => { + (COMPONENT_RADIO_QUIT, key) if key == &MSG_KEY_ESC => { self.umount_quit(); None } // -- text size error; block everything (COMPONENT_TEXT_SIZE_ERR, _) => None, // bookmarks - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_TAB) - | (COMPONENT_RECENTS_LIST, &MSG_KEY_TAB) => { + (COMPONENT_BOOKMARKS_LIST, key) | (COMPONENT_RECENTS_LIST, key) + if key == &MSG_KEY_TAB => + { // Give focus to address self.view.active(COMPONENT_INPUT_ADDR); None } // Any , go to bookmarks - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { self.view.active(COMPONENT_BOOKMARKS_LIST); None } // On submit on any unhandled (connect) - (_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => { - // Validate fields - match self.collect_host_params() { - Err(err) => { - // mount error - self.mount_error(err); - } - Ok(params) => { - self.save_recent(); - // Set file transfer params to context - self.context_mut().set_ftparams(params); - // Set exit reason - self.exit_reason = Some(super::ExitReason::Connect); - } - } - // Return None - None - } + (_, Msg::OnSubmit(_)) => self.on_unhandled_submit(), + (_, key) if key == &MSG_KEY_ENTER => self.on_unhandled_submit(), // => Quit - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.mount_quit(); None } @@ -367,4 +354,23 @@ impl AuthActivity { } } } + + fn on_unhandled_submit(&mut self) -> Option<(String, Msg)> { + // Validate fields + match self.collect_host_params() { + Err(err) => { + // mount error + self.mount_error(err); + } + Ok(params) => { + self.save_recent(); + // Set file transfer params to context + self.context_mut().set_ftparams(params); + // Set exit reason + self.exit_reason = Some(super::ExitReason::Connect); + } + } + // Return None + None + } } diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index 8ec4763..2f61f94 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -27,17 +27,15 @@ */ // Locals use super::{AuthActivity, Context, FileTransferProtocol}; -use crate::ui::components::{ - bookmark_list::{BookmarkList, BookmarkListPropsBuilder}, - msgbox::{MsgBox, MsgBoxPropsBuilder}, -}; +use crate::ui::components::bookmark_list::{BookmarkList, BookmarkListPropsBuilder}; use crate::utils::ui::draw_area_in; // Ext -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, label::{Label, LabelPropsBuilder}, + list::{List, ListPropsBuilder}, + paragraph::{Paragraph, ParagraphPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, textarea::{Textarea, TextareaPropsBuilder}, }; @@ -47,7 +45,7 @@ use tuirealm::tui::{ widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{InputType, PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}, + props::{Alignment, InputType, PropsBuilder, TableBuilder, TextSpan}, Msg, Payload, Value, }; @@ -91,19 +89,11 @@ impl AuthActivity { Box::new(Span::new( SpanPropsBuilder::default() .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - TextSpanBuilder::new(" to show keybindings; ") - .bold() - .build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - TextSpanBuilder::new(" to enter setup").bold().build(), + TextSpan::new("Press ").bold(), + TextSpan::new("").bold().fg(key_color), + TextSpan::new(" to show keybindings; ").bold(), + TextSpan::new("").bold().fg(key_color), + TextSpan::new(" to enter setup").bold(), ]) .build(), )), @@ -118,15 +108,8 @@ impl AuthActivity { .with_color(protocol_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, protocol_color) - .with_options( - Some(String::from("Protocol")), - vec![ - String::from("SFTP"), - String::from("SCP"), - String::from("FTP"), - String::from("FTPS"), - ], - ) + .with_title("Protocol", Alignment::Left) + .with_options(&["SFTP", "SCP", "FTP", "FTPS"]) .with_value(Self::protocol_enum_to_opt(default_protocol)) .build(), )), @@ -138,7 +121,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(addr_color) .with_borders(Borders::ALL, BorderType::Rounded, addr_color) - .with_label(String::from("Remote host")) + .with_label("Remote host", Alignment::Left) .build(), )), ); @@ -149,7 +132,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(port_color) .with_borders(Borders::ALL, BorderType::Rounded, port_color) - .with_label(String::from("Port number")) + .with_label("Port number", Alignment::Left) .with_input(InputType::Number) .with_input_len(5) .with_value(Self::get_default_port_for_protocol(default_protocol).to_string()) @@ -163,7 +146,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(username_color) .with_borders(Borders::ALL, BorderType::Rounded, username_color) - .with_label(String::from("Username")) + .with_label("Username", Alignment::Left) .build(), )), ); @@ -174,7 +157,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(password_color) .with_borders(Borders::ALL, BorderType::Rounded, password_color) - .with_label(String::from("Password")) + .with_label("Password", Alignment::Left) .with_input(InputType::Password) .build(), )), @@ -193,7 +176,7 @@ impl AuthActivity { .with_foreground(Color::Yellow) .with_spans(vec![ TextSpan::from("termscp "), - TextSpanBuilder::new(version.as_str()).underlined().bold().build(), + TextSpan::new(version.as_str()).underlined().bold(), TextSpan::from(" is NOW available! Get it from ; view release notes with "), ]) .build(), @@ -208,7 +191,7 @@ impl AuthActivity { .with_background(bookmarks_color) .with_foreground(Color::Black) .with_borders(Borders::ALL, BorderType::Plain, bookmarks_color) - .with_bookmarks(Some(String::from("Bookmarks")), vec![]) + .with_title("Bookmarks", Alignment::Left) .build(), )), ); @@ -220,7 +203,7 @@ impl AuthActivity { .with_background(recents_color) .with_foreground(Color::Black) .with_borders(Borders::ALL, BorderType::Plain, recents_color) - .with_bookmarks(Some(String::from("Recent connections")), vec![]) + .with_title("Recent connections", Alignment::Left) .build(), )), ); @@ -426,7 +409,7 @@ impl AuthActivity { let msg = self.view.update( super::COMPONENT_BOOKMARKS_LIST, BookmarkListPropsBuilder::from(props) - .with_bookmarks(Some(String::from("Bookmarks")), bookmarks) + .with_bookmarks(bookmarks) .build(), ); msg @@ -464,7 +447,7 @@ impl AuthActivity { let msg = self.view.update( super::COMPONENT_RECENTS_LIST, BookmarkListPropsBuilder::from(props) - .with_bookmarks(Some(String::from("Recent connections")), bookmarks) + .with_bookmarks(bookmarks) .build(), ); msg @@ -482,12 +465,13 @@ impl AuthActivity { let err_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(err_color) .with_borders(Borders::ALL, BorderType::Thick, err_color) .bold() - .with_texts(None, vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]) .build(), )), ); @@ -510,17 +494,15 @@ impl AuthActivity { let err_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_SIZE_ERR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(err_color) .with_borders(Borders::ALL, BorderType::Thick, err_color) .bold() - .with_texts( - None, - vec![TextSpan::from( - "termscp requires at least 24 lines of height to run", - )], - ) + .with_texts(vec![TextSpan::from( + "termscp requires at least 24 lines of height to run", + )]) + .with_text_alignment(Alignment::Center) .build(), )), ); @@ -548,10 +530,8 @@ impl AuthActivity { .with_color(quit_color) .with_borders(Borders::ALL, BorderType::Rounded, quit_color) .with_inverted_color(Color::Black) - .with_options( - Some(String::from("Quit termscp?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Quit termscp?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -577,10 +557,8 @@ impl AuthActivity { .with_color(warn_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, warn_color) - .with_options( - Some(String::from("Delete bookmark?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete bookmark?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) .build(), )), @@ -610,10 +588,8 @@ impl AuthActivity { .with_color(warn_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, warn_color) - .with_options( - Some(String::from("Delete bookmark?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete bookmark?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) .build(), )), @@ -640,7 +616,7 @@ impl AuthActivity { Box::new(Input::new( InputPropsBuilder::default() .with_foreground(save_color) - .with_label(String::from("Save bookmark as…")) + .with_label("Save bookmark as…", Alignment::Center) .with_borders( Borders::TOP | Borders::RIGHT | Borders::LEFT, BorderType::Rounded, @@ -659,10 +635,8 @@ impl AuthActivity { BorderType::Rounded, Color::Reset, ) - .with_options( - Some(String::from("Save password?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Save password?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -685,77 +659,38 @@ impl AuthActivity { let key_color = self.theme().misc_keys; self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Scrolltable::new( - ScrollTablePropsBuilder::default() + Box::new(List::new( + ListPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .with_highlighted_str(Some("?")) .with_max_scroll_step(8) + .scrollable(true) .bold() - .with_table( - Some(String::from("Help")), + .with_title("Help", Alignment::Center) + .with_rows( TableBuilder::default() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Quit termscp")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Switch from form and bookmarks")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Switch bookmark tab")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Move up/down in current tab")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Connect/Load bookmark")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Delete selected bookmark")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Enter setup")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Save bookmark")) .build(), ) @@ -786,7 +721,8 @@ impl AuthActivity { Box::new(Textarea::new( TextareaPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_texts(Some(String::from("Release notes")), spans) + .with_title("Release notes", Alignment::Center) + .with_texts(spans) .build(), )), ); diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index fb6e828..3dc40fc 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -42,9 +42,9 @@ use crate::ui::components::{file_list::FileListPropsBuilder, logbox::LogboxProps use crate::ui::keymap::*; use crate::utils::fmt::fmt_path_elide_ex; // externals +use tui_realm_stdlib::progress_bar::ProgressBarPropsBuilder; use tuirealm::{ - components::progress_bar::ProgressBarPropsBuilder, - props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}, + props::{Alignment, PropsBuilder, TableBuilder, TextSpan}, tui::style::Color, Msg, Payload, Update, Value, }; @@ -63,13 +63,13 @@ impl Update for FileTransferActivity { None => None, // Exit after None Some(msg) => match msg { // -- local tab - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_RIGHT) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_RIGHT => { // Change tab self.view.active(COMPONENT_EXPLORER_REMOTE); self.browser.change_tab(FileExplorerTab::Remote); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_BACKSPACE) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_BACKSPACE => { // Go to previous directory self.action_go_to_previous_local_dir(false); if self.browser.sync_browsing { @@ -98,11 +98,11 @@ impl Update for FileTransferActivity { None } } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SPACE) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_SPACE => { self.action_local_send(); self.update_remote_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_A => { // Toggle hidden files self.local_mut().toggle_hidden_files(); // Update status bar @@ -110,24 +110,24 @@ impl Update for FileTransferActivity { // Reload file list component self.update_local_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_I) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_I => { if let SelectedEntry::One(file) = self.get_local_selected_entries() { self.mount_file_info(&file); } None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_L) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_L => { // Reload directory self.reload_local_dir(); // Reload file list component self.update_local_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_O) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_O => { self.action_edit_local_file(); // Reload file list component self.update_local_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_U) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_U => { self.action_go_to_local_upper_dir(false); if self.browser.sync_browsing { let _ = self.update_remote_filelist(); @@ -136,7 +136,7 @@ impl Update for FileTransferActivity { self.update_local_filelist() } // -- remote tab - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_LEFT) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_LEFT => { // Change tab self.view.active(COMPONENT_EXPLORER_LOCAL); self.browser.change_tab(FileExplorerTab::Local); @@ -162,11 +162,11 @@ impl Update for FileTransferActivity { None } } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_SPACE) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_SPACE => { self.action_remote_recv(); self.update_local_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_BACKSPACE) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_BACKSPACE => { // Go to previous directory self.action_go_to_previous_remote_dir(false); // If sync is enabled update local too @@ -176,7 +176,7 @@ impl Update for FileTransferActivity { // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_A => { // Toggle hidden files self.remote_mut().toggle_hidden_files(); // Update status bar @@ -184,25 +184,25 @@ impl Update for FileTransferActivity { // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_I) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_I => { if let SelectedEntry::One(file) = self.get_remote_selected_entries() { self.mount_file_info(&file); } None } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_L) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_L => { // Reload directory self.reload_remote_dir(); // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_O) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_O => { // Edit file self.action_edit_remote_file(); // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_U) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_U => { self.action_go_to_remote_upper_dir(false); if self.browser.sync_browsing { let _ = self.update_local_filelist(); @@ -211,64 +211,78 @@ impl Update for FileTransferActivity { self.update_remote_filelist() } // -- common explorer keys - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_B) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_B) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_B => + { // Show sorting file self.mount_file_sorting(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_C) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_C) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_C => + { self.mount_copy(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_D) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_D) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_D => + { self.mount_mkdir(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_F) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_F) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_F => + { self.mount_find_input(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_G) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_G) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_G => + { self.mount_goto(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_H) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_H) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_H => + { self.mount_help(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_N) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_N) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_N => + { self.mount_newfile(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Q) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Q) - | (COMPONENT_LOG_BOX, &MSG_KEY_CHAR_Q) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_LOG_BOX, key) + if key == &MSG_KEY_CHAR_Q => + { self.mount_quit(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_R) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_R) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_R => + { // Mount rename self.mount_rename(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_S) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_S) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_S) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_S => + { // Mount save as self.mount_saveas(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_V) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_V) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_V) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_V => + { // View match self.browser.tab() { FileExplorerTab::Local => self.action_open_local(), @@ -279,44 +293,49 @@ impl Update for FileTransferActivity { } None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_W) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_W) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_W) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_W => + { // Open with self.mount_openwith(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_X) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_X) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_X => + { // Mount exec self.mount_exec(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Y) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Y) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_Y => + { // Toggle browser sync self.browser.toggle_sync_browsing(); // Update status bar self.refresh_remote_status_bar(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_ESC) - | (COMPONENT_LOG_BOX, &MSG_KEY_ESC) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_LOG_BOX, key) + if key == &MSG_KEY_ESC => + { self.mount_disconnect(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_DEL) - | (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_E) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_DEL) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_E) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_DEL) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_E) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_E || key == &MSG_KEY_DEL => + { self.mount_radio_delete(); None } // -- find result explorer - (COMPONENT_EXPLORER_FIND, &MSG_KEY_ESC) => { + (COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_ESC => { // Umount find self.umount_find(); // Finalize find @@ -337,7 +356,7 @@ impl Update for FileTransferActivity { _ => None, } } - (COMPONENT_EXPLORER_FIND, &MSG_KEY_SPACE) => { + (COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_SPACE => { // Get entry self.action_find_transfer(None); // Reload files @@ -349,18 +368,19 @@ impl Update for FileTransferActivity { } } // -- switch to log - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_TAB) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_TAB) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_TAB => + { self.view.active(COMPONENT_LOG_BOX); // Active log box None } // -- Log box - (COMPONENT_LOG_BOX, &MSG_KEY_TAB) => { + (COMPONENT_LOG_BOX, key) if key == &MSG_KEY_TAB => { self.view.blur(); // Blur log box None } // -- copy popup - (COMPONENT_INPUT_COPY, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_COPY, key) if key == &MSG_KEY_ESC => { self.umount_copy(); None } @@ -383,7 +403,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_COPY, _) => None, // -- exec popup - (COMPONENT_INPUT_EXEC, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_EXEC, key) if key == &MSG_KEY_ESC => { self.umount_exec(); None } @@ -406,7 +426,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_EXEC, _) => None, // -- find popup - (COMPONENT_INPUT_FIND, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_FIND, key) if key == &MSG_KEY_ESC => { self.umount_find_input(); None } @@ -441,7 +461,7 @@ impl Update for FileTransferActivity { None } // -- goto popup - (COMPONENT_INPUT_GOTO, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_GOTO, key) if key == &MSG_KEY_ESC => { self.umount_goto(); None } @@ -474,7 +494,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_GOTO, _) => None, // -- make directory - (COMPONENT_INPUT_MKDIR, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_MKDIR, key) if key == &MSG_KEY_ESC => { self.umount_mkdir(); None } @@ -494,7 +514,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_MKDIR, _) => None, // -- new file - (COMPONENT_INPUT_NEWFILE, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_NEWFILE, key) if key == &MSG_KEY_ESC => { self.umount_newfile(); None } @@ -514,7 +534,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_NEWFILE, _) => None, // -- open with - (COMPONENT_INPUT_OPEN_WITH, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_OPEN_WITH, key) if key == &MSG_KEY_ESC => { self.umount_openwith(); None } @@ -531,7 +551,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_OPEN_WITH, _) => None, // -- rename - (COMPONENT_INPUT_RENAME, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_RENAME, key) if key == &MSG_KEY_ESC => { self.umount_rename(); None } @@ -553,7 +573,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_RENAME, _) => None, // -- save as - (COMPONENT_INPUT_SAVEAS, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_SAVEAS, key) if key == &MSG_KEY_ESC => { self.umount_saveas(); None } @@ -578,15 +598,18 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_SAVEAS, _) => None, // -- fileinfo - (COMPONENT_LIST_FILEINFO, &MSG_KEY_ENTER) - | (COMPONENT_LIST_FILEINFO, &MSG_KEY_ESC) => { + (COMPONENT_LIST_FILEINFO, key) | (COMPONENT_LIST_FILEINFO, key) + if key == &MSG_KEY_ENTER || key == &MSG_KEY_ESC => + { self.umount_file_info(); None } (COMPONENT_LIST_FILEINFO, _) => None, // -- delete - (COMPONENT_RADIO_DELETE, &MSG_KEY_ESC) - | (COMPONENT_RADIO_DELETE, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { + (COMPONENT_RADIO_DELETE, key) + if key == &MSG_KEY_ESC + || key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) => + { self.umount_radio_delete(); None } @@ -631,8 +654,10 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_DELETE, _) => None, // -- disconnect - (COMPONENT_RADIO_DISCONNECT, &MSG_KEY_ESC) - | (COMPONENT_RADIO_DISCONNECT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { + (COMPONENT_RADIO_DISCONNECT, key) + if key == &MSG_KEY_ESC + || key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) => + { self.umount_disconnect(); None } @@ -643,8 +668,10 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_DISCONNECT, _) => None, // -- quit - (COMPONENT_RADIO_QUIT, &MSG_KEY_ESC) - | (COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { + (COMPONENT_RADIO_QUIT, key) + if key == &MSG_KEY_ESC + || key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) => + { self.umount_quit(); None } @@ -655,8 +682,11 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_QUIT, _) => None, // -- sorting - (COMPONENT_RADIO_SORTING, &MSG_KEY_ESC) - | (COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => { + (COMPONENT_RADIO_SORTING, key) if key == &MSG_KEY_ESC => { + self.umount_file_sorting(); + None + } + (COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => { self.umount_file_sorting(); None } @@ -688,25 +718,31 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_SORTING, _) => None, // -- error - (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { self.umount_error(); None } (COMPONENT_TEXT_ERROR, _) => None, // -- fatal - (COMPONENT_TEXT_FATAL, &MSG_KEY_ESC) | (COMPONENT_TEXT_FATAL, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_FATAL, key) | (COMPONENT_TEXT_FATAL, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { self.exit_reason = Some(super::ExitReason::Disconnect); None } (COMPONENT_TEXT_FATAL, _) => None, // -- help - (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) | (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { self.umount_help(); None } (COMPONENT_TEXT_HELP, _) => None, // -- progress bar - (COMPONENT_PROGRESS_BAR_PARTIAL, &MSG_KEY_CTRL_C) => { + (COMPONENT_PROGRESS_BAR_PARTIAL, key) if key == &MSG_KEY_CTRL_C => { // Set transfer aborted to True self.transfer.abort(); None @@ -752,7 +788,8 @@ impl FileTransferActivity { .collect(); // Update let props = FileListPropsBuilder::from(props) - .with_files(Some(hostname), files) + .with_files(files) + .with_title(hostname, Alignment::Left) .build(); // Update self.view.update(super::COMPONENT_EXPLORER_LOCAL, props) @@ -790,7 +827,8 @@ impl FileTransferActivity { .collect(); // Update let props = FileListPropsBuilder::from(props) - .with_files(Some(hostname), files) + .with_files(files) + .with_title(hostname, Alignment::Left) .build(); self.view.update(super::COMPONENT_EXPLORER_REMOTE, props) } @@ -823,7 +861,7 @@ impl FileTransferActivity { ))) .add_col(TextSpan::from(" [")) .add_col( - TextSpanBuilder::new( + TextSpan::new( format!( "{:5}", match record.level { @@ -834,16 +872,13 @@ impl FileTransferActivity { ) .as_str(), ) - .with_foreground(fg) - .build(), + .fg(fg), ) .add_col(TextSpan::from("]: ")) .add_col(TextSpan::from(record.msg.as_ref())); } let table = table.build(); - let props = LogboxPropsBuilder::from(props) - .with_log(Some(String::from("Log")), table) - .build(); + let props = LogboxPropsBuilder::from(props).with_log(table).build(); self.view.update(super::COMPONENT_LOG_BOX, props) } None => None, @@ -852,9 +887,8 @@ impl FileTransferActivity { pub(super) fn update_progress_bar(&mut self, filename: String) -> Option<(String, Msg)> { if let Some(props) = self.view.get_props(COMPONENT_PROGRESS_BAR_FULL) { - let root_name: String = props.texts.title.as_deref().unwrap_or("").to_string(); let props = ProgressBarPropsBuilder::from(props) - .with_texts(Some(root_name), self.transfer.full.to_string()) + .with_label(self.transfer.full.to_string()) .with_progress(self.transfer.full.calc_progress()) .build(); let _ = self.view.update(COMPONENT_PROGRESS_BAR_FULL, props); @@ -862,7 +896,8 @@ impl FileTransferActivity { match self.view.get_props(COMPONENT_PROGRESS_BAR_PARTIAL) { Some(props) => { let props = ProgressBarPropsBuilder::from(props) - .with_texts(Some(filename), self.transfer.partial.to_string()) + .with_title(filename, Alignment::Center) + .with_label(self.transfer.partial.to_string()) .with_progress(self.transfer.partial.calc_progress()) .build(); self.view.update(COMPONENT_PROGRESS_BAR_PARTIAL, props) @@ -889,7 +924,6 @@ impl FileTransferActivity { match self.view.get_props(COMPONENT_EXPLORER_FIND) { None => None, Some(props) => { - let title: String = props.texts.title.clone().unwrap_or_default(); // Prepare files let files: Vec = self .found() @@ -897,9 +931,7 @@ impl FileTransferActivity { .iter_files() .map(|x: &FsEntry| self.found().unwrap().fmt_file(x)) .collect(); - let props = FileListPropsBuilder::from(props) - .with_files(Some(title), files) - .build(); + let props = FileListPropsBuilder::from(props).with_files(files).build(); self.view.update(COMPONENT_EXPLORER_FIND, props) } } diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index c0685a5..e31bd3f 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -32,7 +32,6 @@ use crate::fs::FsEntry; use crate::ui::components::{ file_list::{FileList, FileListPropsBuilder}, logbox::{LogBox, LogboxPropsBuilder}, - msgbox::{MsgBox, MsgBoxPropsBuilder}, }; use crate::ui::store::Store; use crate::utils::fmt::fmt_time; @@ -40,15 +39,16 @@ use crate::utils::ui::draw_area_in; // Ext use bytesize::ByteSize; use std::path::PathBuf; -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, + list::{List, ListPropsBuilder}, + paragraph::{Paragraph, ParagraphPropsBuilder}, progress_bar::{ProgressBar, ProgressBarPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, table::{Table, TablePropsBuilder}, }; -use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}; +use tuirealm::props::{Alignment, PropsBuilder, TableBuilder, TextSpan}; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, style::Color, @@ -101,6 +101,7 @@ impl FileTransferActivity { super::COMPONENT_LOG_BOX, Box::new(LogBox::new( LogboxPropsBuilder::default() + .with_title("Log", Alignment::Left) .with_background(log_background) .with_borders(Borders::ALL, BorderType::Plain, log_panel) .build(), @@ -383,12 +384,13 @@ impl FileTransferActivity { let error_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(error_color) .with_borders(Borders::ALL, BorderType::Rounded, error_color) .bold() - .with_texts(None, vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]) .build(), )), ); @@ -408,12 +410,13 @@ impl FileTransferActivity { let error_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_FATAL, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(error_color) .with_borders(Borders::ALL, BorderType::Rounded, error_color) .bold() - .with_texts(None, vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]) .build(), )), ); @@ -422,28 +425,26 @@ impl FileTransferActivity { } pub(super) fn mount_wait(&mut self, text: &str) { - self.mount_wait_ex(text, false, Color::Reset); + self.mount_wait_ex(text, Color::Reset); } pub(super) fn mount_blocking_wait(&mut self, text: &str) { - self.mount_wait_ex(text, true, Color::Reset); + self.mount_wait_ex(text, Color::Reset); self.view(); } - fn mount_wait_ex(&mut self, text: &str, blink: bool, color: Color) { + fn mount_wait_ex(&mut self, text: &str, color: Color) { // Mount - let mut builder: MsgBoxPropsBuilder = MsgBoxPropsBuilder::default(); + let mut builder: ParagraphPropsBuilder = ParagraphPropsBuilder::default(); builder .with_foreground(color) .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .bold() - .with_texts(None, vec![TextSpan::from(text)]); - if blink { - builder.blink(); - } + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]); self.view.mount( super::COMPONENT_TEXT_WAIT, - Box::new(MsgBox::new(builder.build())), + Box::new(Paragraph::new(builder.build())), ); // Give focus to info self.view.active(super::COMPONENT_TEXT_WAIT); @@ -466,10 +467,8 @@ impl FileTransferActivity { .with_color(quit_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, quit_color) - .with_options( - Some(String::from("Are you sure you want to quit?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Are you sure you want to quit?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -496,10 +495,8 @@ impl FileTransferActivity { .with_color(quit_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, quit_color) - .with_options( - Some(String::from("Are you sure you want to disconnect?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Are you sure you want to disconnect?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -521,7 +518,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Copy file(s) to…")) + .with_label("Copy file(s) to…", Alignment::Center) .build(), )), ); @@ -540,7 +537,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Execute command")) + .with_label("Execute command", Alignment::Center) .build(), )), ); @@ -570,7 +567,10 @@ impl FileTransferActivity { super::COMPONENT_EXPLORER_FIND, Box::new(FileList::new( FileListPropsBuilder::default() - .with_files(Some(format!("Search results for \"{}\"", search)), vec![]) + .with_title( + format!("Search results for \"{}\"", search), + Alignment::Left, + ) .with_borders(Borders::ALL, BorderType::Plain, hg) .with_highlight_color(hg) .with_background(bg) @@ -594,7 +594,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Search files by name")) + .with_label("Search files by name", Alignment::Center) .build(), )), ); @@ -615,7 +615,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Change working directory")) + .with_label("Change working directory", Alignment::Center) .build(), )), ); @@ -634,7 +634,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Insert directory name")) + .with_label("Insert directory name", Alignment::Center) .build(), )), ); @@ -653,7 +653,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("New file name")) + .with_label("New file name", Alignment::Center) .build(), )), ); @@ -672,7 +672,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Open file with…")) + .with_label("Open file with…", Alignment::Center) .build(), )), ); @@ -691,7 +691,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Move file(s) to…")) + .with_label("Move file(s) to…", Alignment::Center) .build(), )), ); @@ -710,7 +710,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Save as…")) + .with_label("Save as…", Alignment::Center) .build(), )), ); @@ -735,7 +735,7 @@ impl FileTransferActivity { BorderType::Rounded, Color::Reset, ) - .with_texts(Some(root_name), String::new()) + .with_title(root_name, Alignment::Center) .build(), )), ); @@ -750,7 +750,7 @@ impl FileTransferActivity { BorderType::Rounded, Color::Reset, ) - .with_texts(Some(String::from("Please wait")), String::new()) + .with_title("Please wait", Alignment::Center) .build(), )), ); @@ -782,15 +782,13 @@ impl FileTransferActivity { .with_color(sorting_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, sorting_color) - .with_options( - Some(String::from("Sort files by")), - vec![ - String::from("Name"), - String::from("Modify time"), - String::from("Creation time"), - String::from("Size"), - ], - ) + .with_title("Sort files by", Alignment::Center) + .with_options(&[ + String::from("Name"), + String::from("Modify time"), + String::from("Creation time"), + String::from("Size"), + ]) .with_value(index) .build(), )), @@ -811,10 +809,8 @@ impl FileTransferActivity { .with_color(warn_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Plain, warn_color) - .with_options( - Some(String::from("Delete file")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete file", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) .build(), )), @@ -841,54 +837,35 @@ impl FileTransferActivity { None => format!("{}", file.get_abs_path().display()), }; // Make texts - texts.add_col(TextSpan::from("Path: ")).add_col( - TextSpanBuilder::new(path.as_str()) - .with_foreground(Color::Yellow) - .build(), - ); + texts + .add_col(TextSpan::from("Path: ")) + .add_col(TextSpan::new(path.as_str()).fg(Color::Yellow)); if let Some(filetype) = file.get_ftype() { texts .add_row() .add_col(TextSpan::from("File type: ")) - .add_col( - TextSpanBuilder::new(filetype.as_str()) - .with_foreground(Color::LightGreen) - .build(), - ); + .add_col(TextSpan::new(filetype.as_str()).fg(Color::LightGreen)); } let (bsize, size): (ByteSize, usize) = (ByteSize(file.get_size() as u64), file.get_size()); - texts.add_row().add_col(TextSpan::from("Size: ")).add_col( - TextSpanBuilder::new(format!("{} ({})", bsize, size).as_str()) - .with_foreground(Color::Cyan) - .build(), - ); + texts + .add_row() + .add_col(TextSpan::from("Size: ")) + .add_col(TextSpan::new(format!("{} ({})", bsize, size).as_str()).fg(Color::Cyan)); let ctime: String = fmt_time(file.get_creation_time(), "%b %d %Y %H:%M:%S"); let atime: String = fmt_time(file.get_last_access_time(), "%b %d %Y %H:%M:%S"); let mtime: String = fmt_time(file.get_creation_time(), "%b %d %Y %H:%M:%S"); texts .add_row() .add_col(TextSpan::from("Creation time: ")) - .add_col( - TextSpanBuilder::new(ctime.as_str()) - .with_foreground(Color::LightGreen) - .build(), - ); + .add_col(TextSpan::new(ctime.as_str()).fg(Color::LightGreen)); texts .add_row() .add_col(TextSpan::from("Last modified time: ")) - .add_col( - TextSpanBuilder::new(mtime.as_str()) - .with_foreground(Color::LightBlue) - .build(), - ); + .add_col(TextSpan::new(mtime.as_str()).fg(Color::LightBlue)); texts .add_row() .add_col(TextSpan::from("Last access time: ")) - .add_col( - TextSpanBuilder::new(atime.as_str()) - .with_foreground(Color::LightRed) - .build(), - ); + .add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed)); // User #[cfg(target_family = "unix")] let username: String = match file.get_user() { @@ -911,22 +888,21 @@ impl FileTransferActivity { }; #[cfg(target_os = "windows")] let group: String = format!("{}", file.get_group().unwrap_or(0)); - texts.add_row().add_col(TextSpan::from("User: ")).add_col( - TextSpanBuilder::new(username.as_str()) - .with_foreground(Color::LightYellow) - .build(), - ); - texts.add_row().add_col(TextSpan::from("Group: ")).add_col( - TextSpanBuilder::new(group.as_str()) - .with_foreground(Color::Blue) - .build(), - ); + texts + .add_row() + .add_col(TextSpan::from("User: ")) + .add_col(TextSpan::new(username.as_str()).fg(Color::LightYellow)); + texts + .add_row() + .add_col(TextSpan::from("Group: ")) + .add_col(TextSpan::new(group.as_str()).fg(Color::Blue)); self.view.mount( super::COMPONENT_LIST_FILEINFO, Box::new(Table::new( TablePropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) - .with_table(Some(file.get_name().to_string()), texts.build()) + .with_title(file.get_name(), Alignment::Left) + .with_table(texts.build()) .build(), )), ); @@ -941,22 +917,16 @@ impl FileTransferActivity { let sorting_color = self.theme().transfer_status_sorting; let hidden_color = self.theme().transfer_status_hidden; let local_bar_spans: Vec = vec![ - TextSpanBuilder::new("File sorting: ") - .with_foreground(sorting_color) - .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting())) - .with_foreground(sorting_color) - .reversed() - .build(), - TextSpanBuilder::new(" Hidden files: ") - .with_foreground(hidden_color) - .build(), - TextSpanBuilder::new(Self::get_hidden_files_str( + TextSpan::new("File sorting: ").fg(sorting_color), + TextSpan::new(Self::get_file_sorting_str(self.local().get_file_sorting())) + .fg(sorting_color) + .reversed(), + TextSpan::new(" Hidden files: ").fg(hidden_color), + TextSpan::new(Self::get_hidden_files_str( self.local().hidden_files_visible(), )) - .with_foreground(hidden_color) - .reversed() - .build(), + .fg(hidden_color) + .reversed(), ]; if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_LOCAL) { self.view.update( @@ -973,32 +943,23 @@ impl FileTransferActivity { let hidden_color = self.theme().transfer_status_hidden; let sync_color = self.theme().transfer_status_sync_browsing; let remote_bar_spans: Vec = vec![ - TextSpanBuilder::new("File sorting: ") - .with_foreground(sorting_color) - .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) - .with_foreground(sorting_color) - .reversed() - .build(), - TextSpanBuilder::new(" Hidden files: ") - .with_foreground(hidden_color) - .build(), - TextSpanBuilder::new(Self::get_hidden_files_str( + TextSpan::new("File sorting: ").fg(sorting_color), + TextSpan::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) + .fg(sorting_color) + .reversed(), + TextSpan::new(" Hidden files: ").fg(hidden_color), + TextSpan::new(Self::get_hidden_files_str( self.remote().hidden_files_visible(), )) - .with_foreground(hidden_color) - .reversed() - .build(), - TextSpanBuilder::new(" Sync Browsing: ") - .with_foreground(sync_color) - .build(), - TextSpanBuilder::new(match self.browser.sync_browsing { + .fg(hidden_color) + .reversed(), + TextSpan::new(" Sync Browsing: ").fg(sync_color), + TextSpan::new(match self.browser.sync_browsing { true => "ON ", false => "OFF", }) - .with_foreground(sync_color) - .reversed() - .build(), + .fg(sync_color) + .reversed(), ]; if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_REMOTE) { self.view.update( @@ -1017,253 +978,109 @@ impl FileTransferActivity { let key_color = self.theme().misc_keys; self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Scrolltable::new( - ScrollTablePropsBuilder::default() + Box::new(List::new( + ListPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .with_highlighted_str(Some("?")) .with_max_scroll_step(8) .bold() - .with_table( - Some(String::from("Help")), + .scrollable(true) + .with_title("Help", Alignment::Center) + .with_rows( TableBuilder::default() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Disconnect")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Switch between explorer and logs", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Go to previous directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Change explorer tab")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Move up/down in list")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Enter directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Upload/Download file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Toggle hidden files")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Change file sorting mode")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Copy")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Make directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Go to path")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Show help")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Show info about selected file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Reload directory content")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Select file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Create new file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Open text file with preferred editor", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Quit termscp")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Rename file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Save file as")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Go to parent directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Open file with default application for file type", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Open file with specified application", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Execute shell command")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Toggle synchronized browsing")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Delete selected file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Select all files")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Interrupt file transfer")) .build(), ) diff --git a/src/ui/activities/setup/update.rs b/src/ui/activities/setup/update.rs index 0f70114..06593e8 100644 --- a/src/ui/activities/setup/update.rs +++ b/src/ui/activities/setup/update.rs @@ -74,65 +74,67 @@ impl SetupActivity { None => None, Some(msg) => match msg { // Input field - (COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_TEXT_EDITOR, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL); None } - (COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_DEFAULT_PROTOCOL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_HIDDEN_FILES); None } - (COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_HIDDEN_FILES, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_UPDATES); None } - (COMPONENT_RADIO_UPDATES, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_GROUP_DIRS); None } - (COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT); None } - (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_LOCAL_FILE_FMT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT); None } - (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_REMOTE_FILE_FMT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_TEXT_EDITOR); None } // Input field - (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_REMOTE_FILE_FMT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT); None } - (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_LOCAL_FILE_FMT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_GROUP_DIRS); None } - (COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_UP) => { + (COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_UPDATES); None } - (COMPONENT_RADIO_UPDATES, &MSG_KEY_UP) => { + (COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_HIDDEN_FILES); None } - (COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_UP) => { + (COMPONENT_RADIO_HIDDEN_FILES, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL); None } - (COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_UP) => { + (COMPONENT_RADIO_DEFAULT_PROTOCOL, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_INPUT_TEXT_EDITOR); None } - (COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => { + (COMPONENT_INPUT_TEXT_EDITOR, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT); None } // Error or - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount text error self.umount_error(); None @@ -161,7 +163,9 @@ impl SetupActivity { } (COMPONENT_RADIO_QUIT, _) => None, // Close help - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount help self.umount_help(); None @@ -189,12 +193,12 @@ impl SetupActivity { None } // Show help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { // Change view if let Err(err) = self.action_change_tab(ViewLayout::SshKeys) { self.mount_error(err.as_str()); @@ -202,7 +206,7 @@ impl SetupActivity { None } // Revert changes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Revert changes if let Err(err) = self.action_reset_config() { self.mount_error(err.as_str()); @@ -210,13 +214,13 @@ impl SetupActivity { None } // Save - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show save self.mount_save_popup(); None } // - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.action_on_esc(); None } @@ -232,7 +236,9 @@ impl SetupActivity { None => None, Some(msg) => match msg { // Error or - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount text error self.umount_error(); None @@ -261,7 +267,9 @@ impl SetupActivity { } (COMPONENT_RADIO_QUIT, _) => None, // Close help - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount help self.umount_help(); None @@ -300,28 +308,30 @@ impl SetupActivity { (COMPONENT_RADIO_SAVE, _) => None, // Edit SSH Key // Show help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } // New key - (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_SSH_HOST, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_SSH_USERNAME); None } - (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_SSH_USERNAME, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_SSH_HOST); None } // New key - (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_UP) - | (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_TAB) => { + (COMPONENT_INPUT_SSH_USERNAME, key) | (COMPONENT_INPUT_SSH_USERNAME, key) + if key == &MSG_KEY_UP || key == &MSG_KEY_TAB => + { self.view.active(COMPONENT_INPUT_SSH_HOST); None } - (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_UP) - | (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_TAB) => { + (COMPONENT_INPUT_SSH_HOST, key) | (COMPONENT_INPUT_SSH_HOST, key) + if key == &MSG_KEY_UP || key == &MSG_KEY_TAB => + { self.view.active(COMPONENT_INPUT_SSH_USERNAME); None } @@ -335,14 +345,15 @@ impl SetupActivity { None } // New key - (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_ESC) - | (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_SSH_HOST, key) | (COMPONENT_INPUT_SSH_USERNAME, key) + if key == &MSG_KEY_ESC => + { // Umount new ssh key self.umount_new_ssh_key(); None } // New key - (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_N) => { + (COMPONENT_LIST_SSH_KEYS, key) if key == &MSG_KEY_CTRL_N => { // Show new key popup self.mount_new_ssh_key(); None @@ -356,13 +367,14 @@ impl SetupActivity { None } // Show delete - (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_E) - | (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_DEL) => { + (COMPONENT_LIST_SSH_KEYS, key) | (COMPONENT_LIST_SSH_KEYS, key) + if key == &MSG_KEY_CTRL_E || key == &MSG_KEY_DEL => + { // Show delete key self.mount_del_ssh_key(); None } - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { // Change view if let Err(err) = self.action_change_tab(ViewLayout::Theme) { self.mount_error(err.as_str()); @@ -370,7 +382,7 @@ impl SetupActivity { None } // Revert changes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Revert changes if let Err(err) = self.action_reset_config() { self.mount_error(err.as_str()); @@ -378,13 +390,13 @@ impl SetupActivity { None } // Save - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show save self.mount_save_popup(); None } // - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.action_on_esc(); None } @@ -400,217 +412,217 @@ impl SetupActivity { None => None, Some(msg) => match msg { // Input fields - (COMPONENT_COLOR_AUTH_PROTOCOL, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_PROTOCOL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_ADDR); None } - (COMPONENT_COLOR_AUTH_ADDR, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_ADDR, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_PORT); None } - (COMPONENT_COLOR_AUTH_PORT, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_PORT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_USERNAME); None } - (COMPONENT_COLOR_AUTH_USERNAME, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_USERNAME, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_PASSWORD); None } - (COMPONENT_COLOR_AUTH_PASSWORD, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_PASSWORD, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_BOOKMARKS); None } - (COMPONENT_COLOR_AUTH_BOOKMARKS, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_BOOKMARKS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_RECENTS); None } - (COMPONENT_COLOR_AUTH_RECENTS, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_RECENTS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_ERROR); None } - (COMPONENT_COLOR_MISC_ERROR, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_ERROR, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_INPUT); None } - (COMPONENT_COLOR_MISC_INPUT, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_INPUT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_KEYS); None } - (COMPONENT_COLOR_MISC_KEYS, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_KEYS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_QUIT); None } - (COMPONENT_COLOR_MISC_QUIT, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_QUIT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_SAVE); None } - (COMPONENT_COLOR_MISC_SAVE, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_SAVE, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_WARN); None } - (COMPONENT_COLOR_MISC_WARN, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_WARN, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, key) if key == &MSG_KEY_DOWN => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, key) if key == &MSG_KEY_DOWN => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, key) if key == &MSG_KEY_DOWN => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_BG); None } - (COMPONENT_COLOR_TRANSFER_LOG_BG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_LOG_BG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_WIN); None } - (COMPONENT_COLOR_TRANSFER_LOG_WIN, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_LOG_WIN, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SORTING); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN); None } - (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SYNC); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_PROTOCOL); None } - (COMPONENT_COLOR_AUTH_PROTOCOL, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_PROTOCOL, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SYNC); None } - (COMPONENT_COLOR_AUTH_ADDR, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_ADDR, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_PROTOCOL); None } - (COMPONENT_COLOR_AUTH_PORT, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_PORT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_ADDR); None } - (COMPONENT_COLOR_AUTH_USERNAME, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_USERNAME, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_PORT); None } - (COMPONENT_COLOR_AUTH_PASSWORD, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_PASSWORD, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_USERNAME); None } - (COMPONENT_COLOR_AUTH_BOOKMARKS, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_BOOKMARKS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_PASSWORD); None } - (COMPONENT_COLOR_AUTH_RECENTS, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_RECENTS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_BOOKMARKS); None } - (COMPONENT_COLOR_MISC_ERROR, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_ERROR, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_RECENTS); None } - (COMPONENT_COLOR_MISC_INPUT, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_INPUT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_ERROR); None } - (COMPONENT_COLOR_MISC_KEYS, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_KEYS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_INPUT); None } - (COMPONENT_COLOR_MISC_QUIT, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_QUIT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_KEYS); None } - (COMPONENT_COLOR_MISC_SAVE, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_SAVE, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_QUIT); None } - (COMPONENT_COLOR_MISC_WARN, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_WARN, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_SAVE); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_WARN); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, key) if key == &MSG_KEY_UP => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, key) if key == &MSG_KEY_UP => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, key) if key == &MSG_KEY_UP => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL); None } - (COMPONENT_COLOR_TRANSFER_LOG_BG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_LOG_BG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL); None } - (COMPONENT_COLOR_TRANSFER_LOG_WIN, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_LOG_WIN, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_BG); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_WIN); None } - (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SORTING); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN); None } @@ -624,7 +636,9 @@ impl SetupActivity { None } // Error or - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount text error self.umount_error(); None @@ -653,7 +667,9 @@ impl SetupActivity { } (COMPONENT_RADIO_QUIT, _) => None, // Close help - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount help self.umount_help(); None @@ -676,12 +692,12 @@ impl SetupActivity { (COMPONENT_RADIO_SAVE, _) => None, // Edit SSH Key // Show help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { // Change view if let Err(err) = self.action_change_tab(ViewLayout::SetupForm) { self.mount_error(err.as_str()); @@ -689,7 +705,7 @@ impl SetupActivity { None } // Revert changes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Revert changes if let Err(err) = self.action_reset_theme() { self.mount_error(err.as_str()); @@ -697,13 +713,13 @@ impl SetupActivity { None } // Save - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show save self.mount_save_popup(); None } // - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.action_on_esc(); None } diff --git a/src/ui/activities/setup/view/mod.rs b/src/ui/activities/setup/view/mod.rs index a4b6784..393c1b6 100644 --- a/src/ui/activities/setup/view/mod.rs +++ b/src/ui/activities/setup/view/mod.rs @@ -34,14 +34,14 @@ use super::*; pub use setup::*; pub use ssh_keys::*; pub use theme::*; -// Locals -use crate::ui::components::msgbox::{MsgBox, MsgBoxPropsBuilder}; // Ext -use tuirealm::components::{ +use tui_realm_stdlib::{ + list::{List, ListPropsBuilder}, + paragraph::{Paragraph, ParagraphPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - scrolltable::{ScrollTablePropsBuilder, Scrolltable}, + span::{Span, SpanPropsBuilder}, }; -use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}; +use tuirealm::props::{Alignment, PropsBuilder, TableBuilder, TextSpan}; use tuirealm::tui::{ style::Color, widgets::{BorderType, Borders}, @@ -79,12 +79,13 @@ impl SetupActivity { // Mount self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(Color::Red) .bold() .with_borders(Borders::ALL, BorderType::Rounded, Color::Red) - .with_texts(None, vec![TextSpan::from(text)]) + .with_texts(vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) .build(), )), ); @@ -110,16 +111,15 @@ impl SetupActivity { .with_color(Color::LightRed) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) - .with_options( - Some(String::from( - "There are unsaved changes! Save changes before leaving?", - )), - vec![ - String::from("Save"), - String::from("Don't save"), - String::from("Cancel"), - ], + .with_title( + "There are unsaved changes! Save changes before leaving?", + Alignment::Center, ) + .with_options(&[ + String::from("Save"), + String::from("Don't save"), + String::from("Cancel"), + ]) .build(), )), ); @@ -145,10 +145,8 @@ impl SetupActivity { .with_color(Color::LightYellow) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_options( - Some(String::from("Save changes?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Save changes?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -163,91 +161,81 @@ impl SetupActivity { self.view.umount(super::COMPONENT_RADIO_SAVE); } + pub(self) fn mount_header_tab(&mut self, idx: usize) { + self.view.mount( + super::COMPONENT_RADIO_TAB, + Box::new(Radio::new( + RadioPropsBuilder::default() + .with_color(Color::LightYellow) + .with_inverted_color(Color::Black) + .with_borders(Borders::BOTTOM, BorderType::Thick, Color::White) + .with_options(&[ + String::from("User Interface"), + String::from("SSH Keys"), + String::from("Theme"), + ]) + .with_value(idx) + .build(), + )), + ); + } + + pub(self) fn mount_footer(&mut self) { + self.view.mount( + super::COMPONENT_TEXT_FOOTER, + Box::new(Span::new( + SpanPropsBuilder::default() + .with_spans(vec![ + TextSpan::new("Press ").bold(), + TextSpan::new("").bold().fg(Color::Cyan), + TextSpan::new(" to show keybindings").bold(), + ]) + .build(), + )), + ); + } + /// ### mount_help /// /// Mount help pub(super) fn mount_help(&mut self) { self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Scrolltable::new( - ScrollTablePropsBuilder::default() + Box::new(List::new( + ListPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .with_highlighted_str(Some("?")) .with_max_scroll_step(8) .bold() - .with_table( - Some(String::from("Help")), + .with_title("Help", Alignment::Center) + .scrollable(true) + .with_rows( TableBuilder::default() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Exit setup")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Change setup page")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Change cursor")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Change input field")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Select / Dismiss popup")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Delete SSH key")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" New SSH key")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Revert changes")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Save configuration")) .build(), ) diff --git a/src/ui/activities/setup/view/setup.rs b/src/ui/activities/setup/view/setup.rs index 9a8c316..b97b690 100644 --- a/src/ui/activities/setup/view/setup.rs +++ b/src/ui/activities/setup/view/setup.rs @@ -33,10 +33,9 @@ use crate::fs::explorer::GroupDirs; use crate::utils::ui::draw_area_in; // Ext use std::path::PathBuf; -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - span::{Span, SpanPropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -44,7 +43,7 @@ use tuirealm::tui::{ widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{PropsBuilder, TextSpanBuilder}, + props::{Alignment, PropsBuilder}, Payload, Value, View, }; @@ -59,41 +58,9 @@ impl SetupActivity { self.view = View::init(); // Common stuff // Radio tab - self.view.mount( - super::COMPONENT_RADIO_TAB, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(Color::LightYellow) - .with_inverted_color(Color::Black) - .with_borders(Borders::BOTTOM, BorderType::Thick, Color::White) - .with_options( - None, - vec![ - String::from("User Interface"), - String::from("SSH Keys"), - String::from("Theme"), - ], - ) - .with_value(0) - .build(), - )), - ); + self.mount_header_tab(0); // Footer - self.view.mount( - super::COMPONENT_TEXT_FOOTER, - Box::new(Span::new( - SpanPropsBuilder::default() - .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - TextSpanBuilder::new(" to show keybindings").bold().build(), - ]) - .build(), - )), - ); + self.mount_footer(); // Input fields self.view.mount( super::COMPONENT_INPUT_TEXT_EDITOR, @@ -101,7 +68,7 @@ impl SetupActivity { InputPropsBuilder::default() .with_foreground(Color::LightGreen) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen) - .with_label(String::from("Text editor")) + .with_label("Text editor", Alignment::Left) .build(), )), ); @@ -113,15 +80,13 @@ impl SetupActivity { .with_color(Color::LightCyan) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan) - .with_options( - Some(String::from("Default file transfer protocol")), - vec![ - String::from("SFTP"), - String::from("SCP"), - String::from("FTP"), - String::from("FTPS"), - ], - ) + .with_title("Default file transfer protocol", Alignment::Left) + .with_options(&[ + String::from("SFTP"), + String::from("SCP"), + String::from("FTP"), + String::from("FTPS"), + ]) .build(), )), ); @@ -132,10 +97,8 @@ impl SetupActivity { .with_color(Color::LightRed) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) - .with_options( - Some(String::from("Show hidden files (by default)")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Show hidden files (by default)?", Alignment::Left) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -146,10 +109,8 @@ impl SetupActivity { .with_color(Color::LightYellow) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_options( - Some(String::from("Check for updates?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Check for updates?", Alignment::Left) + .with_options(&[String::from("Yes"), String::from("No")]) .build(), )), ); @@ -160,14 +121,12 @@ impl SetupActivity { .with_color(Color::LightMagenta) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightMagenta) - .with_options( - Some(String::from("Group directories")), - vec![ - String::from("Display first"), - String::from("Display Last"), - String::from("No"), - ], - ) + .with_title("Group directories", Alignment::Left) + .with_options(&[ + String::from("Display first"), + String::from("Display Last"), + String::from("No"), + ]) .build(), )), ); @@ -177,7 +136,7 @@ impl SetupActivity { InputPropsBuilder::default() .with_foreground(Color::LightBlue) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue) - .with_label(String::from("File formatter syntax (local)")) + .with_label("File formatter syntax (local)", Alignment::Left) .build(), )), ); @@ -187,7 +146,7 @@ impl SetupActivity { InputPropsBuilder::default() .with_foreground(Color::LightGreen) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen) - .with_label(String::from("File formatter syntax (remote)")) + .with_label("File formatter syntax (remote)", Alignment::Left) .build(), )), ); diff --git a/src/ui/activities/setup/view/ssh_keys.rs b/src/ui/activities/setup/view/ssh_keys.rs index 3517178..7b6ba5c 100644 --- a/src/ui/activities/setup/view/ssh_keys.rs +++ b/src/ui/activities/setup/view/ssh_keys.rs @@ -31,10 +31,9 @@ use super::{Context, SetupActivity}; use crate::ui::components::bookmark_list::{BookmarkList, BookmarkListPropsBuilder}; use crate::utils::ui::draw_area_in; // Ext -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - span::{Span, SpanPropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -42,7 +41,7 @@ use tuirealm::tui::{ widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{PropsBuilder, TextSpanBuilder}, + props::{Alignment, PropsBuilder}, View, }; @@ -57,46 +56,15 @@ impl SetupActivity { self.view = View::init(); // Common stuff // Radio tab - self.view.mount( - super::COMPONENT_RADIO_TAB, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(Color::LightYellow) - .with_inverted_color(Color::Black) - .with_borders(Borders::BOTTOM, BorderType::Thick, Color::LightYellow) - .with_options( - None, - vec![ - String::from("User Interface"), - String::from("SSH Keys"), - String::from("Theme"), - ], - ) - .with_value(1) - .build(), - )), - ); + // Radio tab + self.mount_header_tab(1); // Footer - self.view.mount( - super::COMPONENT_TEXT_FOOTER, - Box::new(Span::new( - SpanPropsBuilder::default() - .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - TextSpanBuilder::new(" to show keybindings").bold().build(), - ]) - .build(), - )), - ); + self.mount_footer(); self.view.mount( super::COMPONENT_LIST_SSH_KEYS, Box::new(BookmarkList::new( BookmarkListPropsBuilder::default() - .with_bookmarks(Some(String::from("SSH Keys")), vec![]) + .with_title("SSH keys", Alignment::Left) .with_borders(Borders::ALL, BorderType::Plain, Color::LightGreen) .with_background(Color::LightGreen) .with_foreground(Color::Black) @@ -211,10 +179,8 @@ impl SetupActivity { .with_color(Color::LightRed) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) - .with_options( - Some(String::from("Delete key?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete key?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) // Default: No .build(), )), @@ -238,7 +204,7 @@ impl SetupActivity { super::COMPONENT_INPUT_SSH_HOST, Box::new(Input::new( InputPropsBuilder::default() - .with_label(String::from("Hostname or address")) + .with_label("Hostname or address", Alignment::Center) .with_borders( Borders::TOP | Borders::RIGHT | Borders::LEFT, BorderType::Plain, @@ -251,7 +217,7 @@ impl SetupActivity { super::COMPONENT_INPUT_SSH_USERNAME, Box::new(Input::new( InputPropsBuilder::default() - .with_label(String::from("Username")) + .with_label("Username", Alignment::Center) .with_borders( Borders::BOTTOM | Borders::RIGHT | Borders::LEFT, BorderType::Plain, @@ -287,7 +253,7 @@ impl SetupActivity { }) .collect(); let props = BookmarkListPropsBuilder::from(props) - .with_bookmarks(Some(String::from("SSH Keys")), keys) + .with_bookmarks(keys) .build(); self.view.update(super::COMPONENT_LIST_SSH_KEYS, props); } diff --git a/src/ui/activities/setup/view/theme.rs b/src/ui/activities/setup/view/theme.rs index 5bb092b..e3af5dc 100644 --- a/src/ui/activities/setup/view/theme.rs +++ b/src/ui/activities/setup/view/theme.rs @@ -33,18 +33,14 @@ use crate::ui::components::color_picker::{ColorPicker, ColorPickerPropsBuilder}; use crate::utils::parser::parse_color; use crate::utils::ui::draw_area_in; // Ext -use tuirealm::components::{ - label::{Label, LabelPropsBuilder}, - radio::{Radio, RadioPropsBuilder}, - span::{Span, SpanPropsBuilder}, -}; +use tui_realm_stdlib::label::{Label, LabelPropsBuilder}; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, style::Color, widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{PropsBuilder, TextSpanBuilder}, + props::{Alignment, PropsBuilder}, Payload, Value, View, }; @@ -59,41 +55,9 @@ impl SetupActivity { self.view = View::init(); // Common stuff // Radio tab - self.view.mount( - super::COMPONENT_RADIO_TAB, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(Color::LightYellow) - .with_inverted_color(Color::Black) - .with_borders(Borders::BOTTOM, BorderType::Thick, Color::White) - .with_options( - None, - vec![ - String::from("User Interface"), - String::from("SSH Keys"), - String::from("Theme"), - ], - ) - .with_value(2) - .build(), - )), - ); + self.mount_header_tab(2); // Footer - self.view.mount( - super::COMPONENT_TEXT_FOOTER, - Box::new(Span::new( - SpanPropsBuilder::default() - .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - TextSpanBuilder::new(" to show keybindings").bold().build(), - ]) - .build(), - )), - ); + self.mount_footer(); // auth colors self.mount_title(super::COMPONENT_COLOR_AUTH_TITLE, "Authentication styles"); self.mount_color_picker(super::COMPONENT_COLOR_AUTH_PROTOCOL, "Protocol"); @@ -653,7 +617,7 @@ impl SetupActivity { Box::new(ColorPicker::new( ColorPickerPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::Reset) - .with_label(label.to_string()) + .with_label(label.to_string(), Alignment::Left) .build(), )), ); diff --git a/src/ui/components/bookmark_list.rs b/src/ui/components/bookmark_list.rs index a541db7..01d81c0 100644 --- a/src/ui/components/bookmark_list.rs +++ b/src/ui/components/bookmark_list.rs @@ -26,18 +26,19 @@ * SOFTWARE. */ // ext -use tuirealm::components::utils::get_block; +use tui_realm_stdlib::utils::get_block; use tuirealm::event::{Event, KeyCode}; -use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan}; +use tuirealm::props::{Alignment, BlockTitle, BordersProps, Props, PropsBuilder}; use tuirealm::tui::{ layout::{Corner, Rect}, style::{Color, Style}, text::Span, widgets::{BorderType, Borders, List, ListItem, ListState}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, PropPayload, PropValue, Value}; // -- props +const PROP_BOOKMARKS: &str = "bookmarks"; pub struct BookmarkListPropsBuilder { props: Option, @@ -117,10 +118,19 @@ impl BookmarkListPropsBuilder { self } - pub fn with_bookmarks(&mut self, title: Option, bookmarks: Vec) -> &mut Self { + pub fn with_title>(&mut self, text: S, alignment: Alignment) -> &mut Self { if let Some(props) = self.props.as_mut() { - let bookmarks: Vec = bookmarks.into_iter().map(TextSpan::from).collect(); - props.texts = TextParts::new(title, Some(bookmarks)); + props.title = Some(BlockTitle::new(text, alignment)); + } + self + } + + pub fn with_bookmarks(&mut self, bookmarks: Vec) -> &mut Self { + if let Some(props) = self.props.as_mut() { + let bookmarks: Vec = bookmarks.into_iter().map(PropValue::Str).collect(); + props + .own + .insert(PROP_BOOKMARKS, PropPayload::Vec(bookmarks)); } self } @@ -210,25 +220,30 @@ impl BookmarkList { // Initialize states let mut states: OwnStates = OwnStates::default(); // Set list length - states.set_list_len(match &props.texts.spans { - Some(tokens) => tokens.len(), - None => 0, - }); + states.set_list_len(Self::bookmarks_len(&props)); BookmarkList { props, states } } + + fn bookmarks_len(props: &Props) -> usize { + match props.own.get(PROP_BOOKMARKS) { + None => 0, + Some(bookmarks) => bookmarks.unwrap_vec().len(), + } + } } impl Component for BookmarkList { #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { if self.props.visible { // Make list - let list_item: Vec = match self.props.texts.spans.as_ref() { - None => vec![], - Some(lines) => lines + let list_item: Vec = match self.props.own.get(PROP_BOOKMARKS) { + Some(PropPayload::Vec(lines)) => lines .iter() - .map(|line| ListItem::new(Span::from(line.content.to_string()))) + .map(|x| x.unwrap_str()) + .map(|x| ListItem::new(Span::from(x.to_string()))) .collect(), + _ => vec![], }; let (fg, bg): (Color, Color) = match self.states.focus { true => (self.props.foreground, self.props.background), @@ -241,7 +256,7 @@ impl Component for BookmarkList { List::new(list_item) .block(get_block( &self.props.borders, - &self.props.texts.title, + self.props.title.as_ref(), self.states.focus, )) .start_corner(Corner::TopLeft) @@ -260,10 +275,7 @@ impl Component for BookmarkList { fn update(&mut self, props: Props) -> Msg { self.props = props; // re-Set list length - self.states.set_list_len(match &self.props.texts.spans { - Some(tokens) => tokens.len(), - None => 0, - }); + self.states.set_list_len(Self::bookmarks_len(&self.props)); // Reset list index self.states.reset_list_index(); Msg::None @@ -347,20 +359,24 @@ mod tests { .with_foreground(Color::Red) .with_background(Color::Blue) .with_borders(Borders::ALL, BorderType::Double, Color::Red) - .with_bookmarks( - Some(String::from("filelist")), - vec![String::from("file1"), String::from("file2")], - ) + .with_title("filelist", Alignment::Left) + .with_bookmarks(vec![String::from("file1"), String::from("file2")]) .build(), ); assert_eq!(component.props.foreground, Color::Red); assert_eq!(component.props.background, Color::Blue); assert_eq!(component.props.visible, true); + assert_eq!(component.props.title.as_ref().unwrap().text(), "filelist"); assert_eq!( - component.props.texts.title.as_ref().unwrap().as_str(), - "filelist" + component + .props + .own + .get(PROP_BOOKMARKS) + .unwrap() + .unwrap_vec() + .len(), + 2 ); - assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2); // Verify states assert_eq!(component.states.list_index, 0); assert_eq!(component.states.list_len, 2); @@ -384,14 +400,11 @@ mod tests { // Update component.update( BookmarkListPropsBuilder::from(component.get_props()) - .with_bookmarks( - Some(String::from("filelist")), - vec![ - String::from("file1"), - String::from("file2"), - String::from("file3"), - ], - ) + .with_bookmarks(vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ]) .build(), ); // Verify states diff --git a/src/ui/components/color_picker.rs b/src/ui/components/color_picker.rs index 2fc9df8..c1ba2bb 100644 --- a/src/ui/components/color_picker.rs +++ b/src/ui/components/color_picker.rs @@ -30,15 +30,15 @@ use crate::utils::fmt::fmt_color; use crate::utils::parser::parse_color; // ext -use tuirealm::components::input::{Input, InputPropsBuilder}; +use tui_realm_stdlib::input::{Input, InputPropsBuilder}; use tuirealm::event::Event; -use tuirealm::props::{Props, PropsBuilder}; +use tuirealm::props::{Alignment, Props, PropsBuilder}; use tuirealm::tui::{ layout::Rect, style::Color, widgets::{BorderType, Borders}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, Value}; // -- props @@ -98,8 +98,8 @@ impl ColorPickerPropsBuilder { /// ### with_label /// /// Set input label - pub fn with_label(&mut self, label: String) -> &mut Self { - self.puppet.with_label(label); + pub fn with_label>(&mut self, label: S, alignment: Alignment) -> &mut Self { + self.puppet.with_label(label, alignment); self } @@ -149,7 +149,7 @@ impl Component for ColorPicker { /// Based on the current properties and states, renders a widget using the provided render engine in the provided Area /// If focused, cursor is also set (if supported by widget) #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { self.input.render(render, area); } @@ -260,6 +260,7 @@ mod test { .visible() .with_color(&Color::Rgb(204, 170, 0)) .with_borders(Borders::ALL, BorderType::Double, Color::Rgb(204, 170, 0)) + .with_label("omar", Alignment::Left) .build(), ); // Focus diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index a0db059..9600659 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -26,10 +26,10 @@ * SOFTWARE. */ // ext -use tuirealm::components::utils::get_block; +use tui_realm_stdlib::utils::get_block; use tuirealm::event::{Event, KeyCode, KeyModifiers}; use tuirealm::props::{ - BordersProps, PropPayload, PropValue, Props, PropsBuilder, TextParts, TextSpan, + Alignment, BlockTitle, BordersProps, PropPayload, PropValue, Props, PropsBuilder, }; use tuirealm::tui::{ layout::{Corner, Rect}, @@ -37,11 +37,12 @@ use tuirealm::tui::{ text::Span, widgets::{BorderType, Borders, List, ListItem, ListState}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, Value}; // -- props -const PROP_HIGHLIGHT_COLOR: &str = "props-highlight-color"; +const PROP_FILES: &str = "files"; +const PALETTE_HIGHLIGHT_COLOR: &str = "props-highlight-color"; pub struct FileListPropsBuilder { props: Option, @@ -107,10 +108,7 @@ impl FileListPropsBuilder { /// Set highlighted color pub fn with_highlight_color(&mut self, color: Color) -> &mut Self { if let Some(props) = self.props.as_mut() { - props.own.insert( - PROP_HIGHLIGHT_COLOR, - PropPayload::One(PropValue::Color(color)), - ); + props.palette.insert(PALETTE_HIGHLIGHT_COLOR, color); } self } @@ -134,10 +132,17 @@ impl FileListPropsBuilder { self } - pub fn with_files(&mut self, title: Option, files: Vec) -> &mut Self { + pub fn with_title>(&mut self, text: S, alignment: Alignment) -> &mut Self { if let Some(props) = self.props.as_mut() { - let files: Vec = files.into_iter().map(TextSpan::from).collect(); - props.texts = TextParts::new(title, Some(files)); + props.title = Some(BlockTitle::new(text, alignment)); + } + self + } + + pub fn with_files(&mut self, files: Vec) -> &mut Self { + if let Some(props) = self.props.as_mut() { + let files: Vec = files.into_iter().map(PropValue::Str).collect(); + props.own.insert(PROP_FILES, PropPayload::Vec(files)); } self } @@ -299,32 +304,39 @@ impl FileList { // Initialize states let mut states: OwnStates = OwnStates::default(); // Init list states - states.init_list_states(props.texts.spans.as_ref().map(|x| x.len()).unwrap_or(0)); + states.init_list_states(Self::files_len(&props)); FileList { props, states } } + + fn files_len(props: &Props) -> usize { + match props.own.get(PROP_FILES) { + None => 0, + Some(files) => files.unwrap_vec().len(), + } + } } impl Component for FileList { #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { if self.props.visible { // Make list - let list_item: Vec = match self.props.texts.spans.as_ref() { - None => vec![], - Some(lines) => lines + let list_item: Vec = match self.props.own.get(PROP_FILES) { + Some(PropPayload::Vec(lines)) => lines .iter() .enumerate() .map(|(num, line)| { let to_display: String = match self.states.is_selected(num) { - true => format!("*{}", line.content), - false => line.content.to_string(), + true => format!("*{}", line.unwrap_str()), + false => line.unwrap_str().to_string(), }; ListItem::new(Span::from(to_display)) }) .collect(), + _ => vec![], }; - let highlighted_color: Color = match self.props.own.get(PROP_HIGHLIGHT_COLOR) { - Some(PropPayload::One(PropValue::Color(c))) => *c, + let highlighted_color: Color = match self.props.palette.get(PALETTE_HIGHLIGHT_COLOR) { + Some(c) => *c, _ => Color::Reset, }; let (h_fg, h_bg): (Color, Color) = match self.states.focus { @@ -338,7 +350,7 @@ impl Component for FileList { List::new(list_item) .block(get_block( &self.props.borders, - &self.props.texts.title, + self.props.title.as_ref(), self.states.focus, )) .start_corner(Corner::TopLeft) @@ -362,14 +374,7 @@ impl Component for FileList { fn update(&mut self, props: Props) -> Msg { self.props = props; // re-Set list states - self.states.init_list_states( - self.props - .texts - .spans - .as_ref() - .map(|x| x.len()) - .unwrap_or(0), - ); + self.states.init_list_states(Self::files_len(&self.props)); Msg::None } @@ -551,24 +556,33 @@ mod tests { .with_background(Color::Blue) .with_highlight_color(Color::LightRed) .with_borders(Borders::ALL, BorderType::Double, Color::Red) - .with_files( - Some(String::from("files")), - vec![String::from("file1"), String::from("file2")], - ) + .with_title("files", Alignment::Left) + .with_files(vec![String::from("file1"), String::from("file2")]) .build(), ); assert_eq!( - *component.props.own.get(PROP_HIGHLIGHT_COLOR).unwrap(), - PropPayload::One(PropValue::Color(Color::LightRed)) + *component + .props + .palette + .get(PALETTE_HIGHLIGHT_COLOR) + .unwrap(), + Color::LightRed ); assert_eq!(component.props.foreground, Color::Red); assert_eq!(component.props.background, Color::Blue); assert_eq!(component.props.visible, true); + assert_eq!(component.props.title.as_ref().unwrap().text(), "files"); assert_eq!( - component.props.texts.title.as_ref().unwrap().as_str(), - "files" + component + .props + .own + .get(PROP_FILES) + .as_ref() + .unwrap() + .unwrap_vec() + .len(), + 2 ); - assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2); // Verify states assert_eq!(component.states.list_index, 0); assert_eq!(component.states.selected.len(), 0); @@ -594,14 +608,11 @@ mod tests { // Update component.update( FileListPropsBuilder::from(component.get_props()) - .with_files( - Some(String::from("filelist")), - vec![ - String::from("file1"), - String::from("file2"), - String::from("file3"), - ], - ) + .with_files(vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ]) .build(), ); // Verify states @@ -670,14 +681,11 @@ mod tests { // Make component let mut component: FileList = FileList::new( FileListPropsBuilder::default() - .with_files( - Some(String::from("files")), - vec![ - String::from("file1"), - String::from("file2"), - String::from("file3"), - ], - ) + .with_files(vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ]) .build(), ); // Get state @@ -735,10 +743,7 @@ mod tests { // Update files component.update( FileListPropsBuilder::from(component.get_props()) - .with_files( - Some(String::from("filelist")), - vec![String::from("file1"), String::from("file2")], - ) + .with_files(vec![String::from("file1"), String::from("file2")]) .build(), ); // Selection should now be empty diff --git a/src/ui/components/logbox.rs b/src/ui/components/logbox.rs index 8e134a5..69f5117 100644 --- a/src/ui/components/logbox.rs +++ b/src/ui/components/logbox.rs @@ -26,18 +26,22 @@ * SOFTWARE. */ // ext -use tuirealm::components::utils::{get_block, wrap_spans}; +use tui_realm_stdlib::utils::{get_block, wrap_spans}; use tuirealm::event::{Event, KeyCode}; -use tuirealm::props::{BordersProps, Props, PropsBuilder, Table as TextTable, TextParts}; +use tuirealm::props::{ + Alignment, BlockTitle, BordersProps, Props, PropsBuilder, Table as TextTable, +}; use tuirealm::tui::{ layout::{Corner, Rect}, style::{Color, Style}, widgets::{BorderType, Borders, List, ListItem, ListState}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, PropPayload, PropValue, Value}; // -- props +const PROP_TABLE: &str = "table"; + pub struct LogboxPropsBuilder { props: Option, } @@ -106,9 +110,18 @@ impl LogboxPropsBuilder { self } - pub fn with_log(&mut self, title: Option, table: TextTable) -> &mut Self { + pub fn with_title>(&mut self, text: S, alignment: Alignment) -> &mut Self { if let Some(props) = self.props.as_mut() { - props.texts = TextParts::table(title, table); + props.title = Some(BlockTitle::new(text, alignment)); + } + self + } + + pub fn with_log(&mut self, table: TextTable) -> &mut Self { + if let Some(props) = self.props.as_mut() { + props + .own + .insert(PROP_TABLE, PropPayload::One(PropValue::Table(table))); } self } @@ -198,33 +211,37 @@ impl LogBox { // Initialize states let mut states: OwnStates = OwnStates::default(); // Set list length - states.set_list_len(match &props.texts.table { - Some(rows) => rows.len(), - None => 0, - }); + states.set_list_len(Self::table_len(&props)); // Reset list index states.reset_list_index(); LogBox { props, states } } + + fn table_len(props: &Props) -> usize { + match props.own.get(PROP_TABLE) { + Some(PropPayload::One(PropValue::Table(table))) => table.len(), + _ => 0, + } + } } impl Component for LogBox { #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, 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 + let list_items: Vec = match self.props.own.get(PROP_TABLE) { + Some(PropPayload::One(PropValue::Table(table))) => table .iter() .map(|row| ListItem::new(wrap_spans(row, width, &self.props))) .collect(), // Make List item from TextSpan + _ => Vec::new(), }; let w = List::new(list_items) .block(get_block( &self.props.borders, - &self.props.texts.title, + self.props.title.as_ref(), self.states.focus, )) .start_corner(Corner::BottomLeft) @@ -240,10 +257,7 @@ impl Component for LogBox { fn update(&mut self, props: Props) -> Msg { self.props = props; // re-Set list length - self.states.set_list_len(match &self.props.texts.table { - Some(rows) => rows.len(), - None => 0, - }); + self.states.set_list_len(Self::table_len(&self.props)); // Reset list index self.states.reset_list_index(); Msg::None @@ -323,8 +337,8 @@ mod tests { .visible() .with_borders(Borders::ALL, BorderType::Double, Color::Red) .with_background(Color::Blue) + .with_title("log", Alignment::Left) .with_log( - Some(String::from("Log")), TableBuilder::default() .add_col(TextSpan::from("12:29")) .add_col(TextSpan::from("system crashed")) @@ -337,11 +351,7 @@ mod tests { ); assert_eq!(component.props.visible, true); assert_eq!(component.props.background, Color::Blue); - assert_eq!( - component.props.texts.title.as_ref().unwrap().as_str(), - "Log" - ); - assert_eq!(component.props.texts.table.as_ref().unwrap().len(), 2); + assert_eq!(component.props.title.as_ref().unwrap().text(), "Log"); // Verify states assert_eq!(component.states.list_index, 0); assert_eq!(component.states.list_len, 2); @@ -364,7 +374,6 @@ mod tests { component.update( LogboxPropsBuilder::from(component.get_props()) .with_log( - Some(String::from("Log")), TableBuilder::default() .add_col(TextSpan::from("12:29")) .add_col(TextSpan::from("system crashed")) diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index a613ff9..bcb878a 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -30,4 +30,3 @@ pub mod bookmark_list; pub mod color_picker; pub mod file_list; pub mod logbox; -pub mod msgbox; diff --git a/src/ui/components/msgbox.rs b/src/ui/components/msgbox.rs deleted file mode 100644 index 226864a..0000000 --- a/src/ui/components/msgbox.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! ## MsgBox -//! -//! `MsgBox` component renders a simple readonly no event associated centered text - -/** - * 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 crate::utils::fmt::align_text_center; -// ext -use tuirealm::components::utils::{get_block, use_or_default_styles}; -use tuirealm::event::Event; -use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan}; -use tuirealm::tui::{ - layout::{Corner, Rect}, - style::{Color, Modifier, Style}, - text::{Span, Spans}, - widgets::{BorderType, Borders, List, ListItem}, -}; -use tuirealm::{Canvas, Component, Msg, Payload}; - -// -- Props - -pub struct MsgBoxPropsBuilder { - props: Option, -} - -impl Default for MsgBoxPropsBuilder { - fn default() -> Self { - MsgBoxPropsBuilder { - props: Some(Props::default()), - } - } -} - -impl PropsBuilder for MsgBoxPropsBuilder { - fn build(&mut self) -> Props { - self.props.take().unwrap() - } - - fn hidden(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.visible = false; - } - self - } - - fn visible(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.visible = true; - } - self - } -} - -impl From for MsgBoxPropsBuilder { - fn from(props: Props) -> Self { - MsgBoxPropsBuilder { props: Some(props) } - } -} - -impl MsgBoxPropsBuilder { - pub fn with_foreground(&mut self, color: Color) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.foreground = color; - } - self - } - - pub fn bold(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.modifiers |= Modifier::BOLD; - } - self - } - - pub fn blink(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.modifiers |= Modifier::SLOW_BLINK; - } - self - } - - pub fn with_borders( - &mut self, - borders: Borders, - variant: BorderType, - color: Color, - ) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.borders = BordersProps { - borders, - variant, - color, - } - } - self - } - - pub fn with_texts(&mut self, title: Option, texts: Vec) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.texts = TextParts::new(title, Some(texts)); - } - self - } -} - -// -- component - -pub struct MsgBox { - props: Props, -} - -impl MsgBox { - /// ### new - /// - /// Instantiate a new Text component - pub fn new(props: Props) -> Self { - MsgBox { props } - } -} - -impl Component for MsgBox { - #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { - // Make a Span - if self.props.visible { - let lines: Vec = match self.props.texts.spans.as_ref() { - None => Vec::new(), - Some(rows) => { - let mut lines: Vec = Vec::new(); - for line in rows.iter() { - // Keep line color, or use default - let (fg, bg, modifiers) = use_or_default_styles(&self.props, line); - let message_row = - textwrap::wrap(line.content.as_str(), area.width as usize); - for msg in message_row.iter() { - lines.push(ListItem::new(Spans::from(vec![Span::styled( - align_text_center(msg, area.width), - Style::default().add_modifier(modifiers).fg(fg).bg(bg), - )]))); - } - } - lines - } - }; - render.render_widget( - List::new(lines) - .block(get_block( - &self.props.borders, - &self.props.texts.title, - true, - )) - .start_corner(Corner::TopLeft) - .style( - Style::default() - .fg(self.props.foreground) - .bg(self.props.background), - ), - area, - ); - } - } - - fn update(&mut self, props: Props) -> Msg { - self.props = props; - // Return None - Msg::None - } - - fn get_props(&self) -> Props { - self.props.clone() - } - - fn on(&mut self, ev: Event) -> Msg { - // Return key - if let Event::Key(key) = ev { - Msg::OnKey(key) - } else { - Msg::None - } - } - - fn get_state(&self) -> Payload { - Payload::None - } - - fn blur(&mut self) {} - - fn active(&mut self) {} -} - -#[cfg(test)] -mod tests { - - use super::*; - - use pretty_assertions::assert_eq; - use tuirealm::event::{KeyCode, KeyEvent}; - use tuirealm::props::{TextSpan, TextSpanBuilder}; - use tuirealm::tui::style::Color; - - #[test] - fn test_ui_components_msgbox() { - let mut component: MsgBox = MsgBox::new( - MsgBoxPropsBuilder::default() - .hidden() - .visible() - .with_foreground(Color::Red) - .bold() - .blink() - .with_borders(Borders::ALL, BorderType::Double, Color::Red) - .with_texts( - None, - vec![ - TextSpan::from("Press "), - TextSpanBuilder::new("") - .with_foreground(Color::Cyan) - .bold() - .build(), - TextSpan::from(" to quit"), - ], - ) - .build(), - ); - assert_eq!(component.props.foreground, Color::Red); - assert!(component.props.modifiers.intersects(Modifier::BOLD)); - assert_eq!(component.props.visible, true); - assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 3); - component.active(); - component.blur(); - // Update - let props = MsgBoxPropsBuilder::from(component.get_props()) - .hidden() - .with_foreground(Color::Yellow) - .build(); - assert_eq!(component.update(props), Msg::None); - assert_eq!(component.props.visible, false); - assert_eq!(component.props.foreground, Color::Yellow); - // Get value - assert_eq!(component.get_state(), Payload::None); - // Event - assert_eq!( - component.on(Event::Key(KeyEvent::from(KeyCode::Delete))), - Msg::OnKey(KeyEvent::from(KeyCode::Delete)) - ); - } -} diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 703cd91..44f8f6a 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -100,23 +100,6 @@ pub fn fmt_millis(duration: Duration) -> String { format!("{}.{:0width$}", seconds, millis, width = 3) } -/// align_text_center -/// -/// Align text to center for a given width -pub fn align_text_center(text: &str, width: u16) -> String { - let indent_size: usize = match (width as usize) >= text.len() { - // NOTE: The check prevents underflow - true => (width as usize - text.len()) / 2, - false => 0, - }; - textwrap::indent( - text, - (0..indent_size).map(|_| " ").collect::().as_str(), - ) - .trim_end() - .to_string() -} - /// ### elide_path /// /// Elide a path if longer than width @@ -362,18 +345,6 @@ mod tests { ); } - #[test] - fn test_utils_align_text_center() { - assert_eq!( - align_text_center("hello world!", 24), - String::from(" hello world!") - ); - // Bad case - assert_eq!( - align_text_center("hello world!", 8), - String::from("hello world!") - ); - } #[test] fn test_utils_fmt_millis() { assert_eq!( From 78e4a4899cfb94d19461f6faa1a43c6a48faa1e2 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 17 Aug 2021 12:44:23 +0200 Subject: [PATCH 05/20] Rewind radio groups --- src/ui/activities/auth/view.rs | 5 +++++ src/ui/activities/filetransfer/view.rs | 3 +++ src/ui/activities/setup/view/mod.rs | 3 +++ src/ui/activities/setup/view/setup.rs | 4 ++++ src/ui/activities/setup/view/ssh_keys.rs | 1 + 5 files changed, 16 insertions(+) diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index 2f61f94..ba3adc8 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -111,6 +111,7 @@ impl AuthActivity { .with_title("Protocol", Alignment::Left) .with_options(&["SFTP", "SCP", "FTP", "FTPS"]) .with_value(Self::protocol_enum_to_opt(default_protocol)) + .rewind(true) .build(), )), ); @@ -532,6 +533,7 @@ impl AuthActivity { .with_inverted_color(Color::Black) .with_title("Quit termscp?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -560,6 +562,7 @@ impl AuthActivity { .with_title("Delete bookmark?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) + .rewind(true) .build(), )), ); @@ -591,6 +594,7 @@ impl AuthActivity { .with_title("Delete bookmark?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) + .rewind(true) .build(), )), ); @@ -637,6 +641,7 @@ impl AuthActivity { ) .with_title("Save password?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index e31bd3f..840345b 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -469,6 +469,7 @@ impl FileTransferActivity { .with_borders(Borders::ALL, BorderType::Rounded, quit_color) .with_title("Are you sure you want to quit?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -497,6 +498,7 @@ impl FileTransferActivity { .with_borders(Borders::ALL, BorderType::Rounded, quit_color) .with_title("Are you sure you want to disconnect?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -812,6 +814,7 @@ impl FileTransferActivity { .with_title("Delete file", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) + .rewind(true) .build(), )), ); diff --git a/src/ui/activities/setup/view/mod.rs b/src/ui/activities/setup/view/mod.rs index 393c1b6..994ae8f 100644 --- a/src/ui/activities/setup/view/mod.rs +++ b/src/ui/activities/setup/view/mod.rs @@ -120,6 +120,7 @@ impl SetupActivity { String::from("Don't save"), String::from("Cancel"), ]) + .rewind(true) .build(), )), ); @@ -147,6 +148,7 @@ impl SetupActivity { .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) .with_title("Save changes?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -175,6 +177,7 @@ impl SetupActivity { String::from("Theme"), ]) .with_value(idx) + .rewind(true) .build(), )), ); diff --git a/src/ui/activities/setup/view/setup.rs b/src/ui/activities/setup/view/setup.rs index b97b690..218c8fb 100644 --- a/src/ui/activities/setup/view/setup.rs +++ b/src/ui/activities/setup/view/setup.rs @@ -87,6 +87,7 @@ impl SetupActivity { String::from("FTP"), String::from("FTPS"), ]) + .rewind(true) .build(), )), ); @@ -99,6 +100,7 @@ impl SetupActivity { .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) .with_title("Show hidden files (by default)?", Alignment::Left) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -111,6 +113,7 @@ impl SetupActivity { .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) .with_title("Check for updates?", Alignment::Left) .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -127,6 +130,7 @@ impl SetupActivity { String::from("Display Last"), String::from("No"), ]) + .rewind(true) .build(), )), ); diff --git a/src/ui/activities/setup/view/ssh_keys.rs b/src/ui/activities/setup/view/ssh_keys.rs index 7b6ba5c..756c618 100644 --- a/src/ui/activities/setup/view/ssh_keys.rs +++ b/src/ui/activities/setup/view/ssh_keys.rs @@ -182,6 +182,7 @@ impl SetupActivity { .with_title("Delete key?", Alignment::Center) .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) // Default: No + .rewind(true) .build(), )), ); From 6cb1dcaa432b7fa0af5f65ada27abbfdadc7f95c Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 17 Aug 2021 18:05:02 +0200 Subject: [PATCH 06/20] upcoming features --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fd6cde..341ef9b 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,14 @@ Major termscp releases will now be seasonal, so expect 4 major updates during th Planned for *🍁 Autumn update 🍇*: -- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. - **Self-update ⬇️**: In order to increase users updating termscp, I want to provide the possibility to update termscp directly from application, when a new update is available. - **AWS S3 support 🪣**: I'll use `rust-s3` library to implement this. This is really big **Maybe** for the autumn update and might be moved to the Winter update. +- **Prompt before replacing files ☢️**: Possibility to configure whether a prompt should be displayed before replacing files. Planned for *❄️ Winter update ⛄*: - **SMB Support 🎉**: This will require a long time to be implemented, since I'm currently working on a Rust native SMB library, since I don't want to add new C-bindings. ~~Fear the 🦚~~ +- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. Along to new features, termscp developments is now focused on UX and performance improvements, so if you have any suggestion, feel free to open an issue. From 7390bb58c5988fdc055be3e5a0fafe57576b6678 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 11:11:14 +0200 Subject: [PATCH 07/20] Changed FTP library from ftp4 to suppaftp; Handle directory already exists on FTP transfer; mkdir already exists test units --- CHANGELOG.md | 1 + Cargo.lock | 71 ++-- Cargo.toml | 2 +- README.md | 2 +- src/filetransfer/ftp_transfer.rs | 665 +++++++----------------------- src/filetransfer/mod.rs | 2 +- src/filetransfer/scp_transfer.rs | 13 +- src/filetransfer/sftp_transfer.rs | 12 +- src/lib.rs | 2 +- src/utils/parser.rs | 1 + 10 files changed, 218 insertions(+), 553 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edc3d5b..0bc4276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Released on ?? - Updated `crossterm` to `0.20` - Updated `open` to `2.0.1` - Added `tui-realm-stdlib 0.6.0` + - Replaced `ftp4` with `suppaftp 4.1.1` - Updated `tui-realm` to `0.6.0` ## 0.6.0 diff --git a/Cargo.lock b/Cargo.lock index 1517e69..ea2f5b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,18 +409,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "ftp4" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e03634a7a0e74618f9adf1e088495caa54ea07e72d449813e6439ce8ac9906f" -dependencies = [ - "chrono", - "lazy_static", - "native-tls", - "regex", -] - [[package]] name = "generic-array" version = "0.14.4" @@ -521,9 +509,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" +checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d" dependencies = [ "wasm-bindgen", ] @@ -647,9 +635,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "mio" @@ -800,9 +788,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.35" +version = "0.10.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" +checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -820,9 +808,9 @@ checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" [[package]] name = "openssl-sys" -version = "0.9.65" +version = "0.9.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" +checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" dependencies = [ "autocfg", "cc", @@ -1334,6 +1322,19 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "suppaftp" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d8310cb2dcc312f9e941d35453a1b2cb654186f4ec60b02e2d778a221c3002b" +dependencies = [ + "chrono", + "lazy_static", + "native-tls", + "regex", + "thiserror", +] + [[package]] name = "syn" version = "1.0.74" @@ -1380,7 +1381,6 @@ dependencies = [ "crossterm", "dirs", "edit", - "ftp4", "hostname", "keyring", "lazy_static", @@ -1395,6 +1395,7 @@ dependencies = [ "serde", "simplelog", "ssh2", + "suppaftp", "tempfile", "textwrap", "thiserror", @@ -1638,9 +1639,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" +checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1648,9 +1649,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" +checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041" dependencies = [ "bumpalo", "lazy_static", @@ -1663,9 +1664,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" +checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1673,9 +1674,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" +checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad" dependencies = [ "proc-macro2", "quote", @@ -1686,15 +1687,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" +checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29" [[package]] name = "web-sys" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" +checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c" dependencies = [ "js-sys", "wasm-bindgen", @@ -1732,9 +1733,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abacf325c958dfeaf1046931d37f2a901b6dfe0968ee965a29e94c6766b2af6" +checksum = "f7741161a40200a867c96dfa5574544efa4178cf4c8f770b62dd1cc0362d7ae1" dependencies = [ "wasm-bindgen", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 7216e80..ec78301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ content_inspector = "0.2.4" crossterm = "0.20" dirs = "3.0.1" edit = "0.1.3" -ftp4 = { version = "4.0.2", features = [ "secure" ] } hostname = "0.3.1" keyring = { version = "0.10.1", optional = true } lazy_static = "1.4.0" @@ -48,6 +47,7 @@ rpassword = "5.0.1" serde = { version = "^1.0.0", features = [ "derive" ] } simplelog = "0.10.0" ssh2 = "0.9.0" +suppaftp = { version = "4.1.1", features = [ "secure" ] } tempfile = "3.1.0" textwrap = "0.14.2" thiserror = "^1.0.0" diff --git a/README.md b/README.md index 341ef9b..49853a2 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,8 @@ termscp is powered by these aweseome projects: - [keyring-rs](https://github.com/hwchen/keyring-rs) - [open-rs](https://github.com/Byron/open-rs) - [rpassword](https://github.com/conradkleinespel/rpassword) -- [rust-ftp](https://github.com/mattnenterprise/rust-ftp) - [ssh2-rs](https://github.com/alexcrichton/ssh2-rs) +- [suppaftp](https://github.com/veeso/suppaftp) - [textwrap](https://github.com/mgeisler/textwrap) - [tui-rs](https://github.com/fdehau/tui-rs) - [tui-realm](https://github.com/veeso/tui-realm) diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 829da89..32d96e3 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -27,18 +27,19 @@ */ use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use crate::fs::{FsDirectory, FsEntry, FsFile}; -use crate::utils::fmt::{fmt_time, shadow_password}; -use crate::utils::parser::{parse_datetime, parse_lstime}; +use crate::utils::fmt::shadow_password; // Includes -use ftp4::native_tls::TlsConnector; -use ftp4::{types::FileType, FtpStream}; -use regex::Regex; +use std::convert::TryFrom; +use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use std::time::SystemTime; -use std::{ - io::{Read, Write}, - ops::Range, +use std::time::UNIX_EPOCH; +use suppaftp::native_tls::TlsConnector; +use suppaftp::{ + list::{File, UnixPexQuery}, + status::FILE_UNAVAILABLE, + types::{FileType, Response}, + FtpError, FtpStream, }; /// ## FtpFileTransfer @@ -71,319 +72,120 @@ impl FtpFileTransfer { p.to_path_buf() } - /// ### parse_list_line + /// ### parse_list_lines /// - /// Parse a line of LIST command output and instantiates an FsEntry from it - fn parse_list_line(&mut self, path: &Path, line: &str) -> Result { - // Try to parse using UNIX syntax - match self.parse_unix_list_line(path, line) { - Ok(entry) => Ok(entry), - Err(_) => match self.parse_dos_list_line(path, line) { - // If UNIX parsing fails, try DOS - Ok(entry) => Ok(entry), - Err(_) => Err(()), - }, - } + /// Parse all lines of LIST command output and instantiates a vector of FsEntry from it. + /// This function also converts from `suppaftp::list::File` to `FsEntry` + fn parse_list_lines(&mut self, path: &Path, lines: Vec) -> Vec { + // Iter and collect + lines + .into_iter() + .map(File::try_from) // Try to convert to file + .flatten() // Remove errors + .map(|x| { + let mut abs_path: PathBuf = path.to_path_buf(); + abs_path.push(x.name()); + match x.is_directory() { + true => FsEntry::Directory(FsDirectory { + name: x.name().to_string(), + abs_path, + last_access_time: x.modified(), + last_change_time: x.modified(), + creation_time: x.modified(), + readonly: false, + symlink: None, + user: x.uid(), + group: x.gid(), + unix_pex: Some(Self::query_unix_pex(&x)), + }), + false => FsEntry::File(FsFile { + name: x.name().to_string(), + size: x.size(), + ftype: abs_path + .extension() + .map(|ext| String::from(ext.to_str().unwrap_or(""))), + last_access_time: x.modified(), + last_change_time: x.modified(), + creation_time: x.modified(), + readonly: false, + user: x.uid(), + group: x.gid(), + symlink: Self::get_symlink_entry(path, x.symlink()), + abs_path, + unix_pex: Some(Self::query_unix_pex(&x)), + }), + } + }) + .collect() } - /// ### parse_unix_list_line + /// ### get_symlink_entry /// - /// Try to parse a "LIST" output command line in UNIX format. - /// Returns error if syntax is not UNIX compliant. - /// UNIX syntax has the following syntax: - /// {FILE_TYPE}{UNIX_PEX} {HARD_LINKS} {USER} {GROUP} {SIZE} {DATE} {FILENAME} - /// -rw-r--r-- 1 cvisintin staff 4968 27 Dic 10:46 CHANGELOG.md - fn parse_unix_list_line(&mut self, path: &Path, line: &str) -> Result { - // Prepare list regex - // NOTE: about this damn regex - lazy_static! { - static ref LS_RE: Regex = Regex::new(r#"^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#).unwrap(); - } - debug!("Parsing LIST (UNIX) line: '{}'", line); - // Apply regex to result - match LS_RE.captures(line) { - // String matches regex - Some(metadata) => { - // NOTE: metadata fmt: (regex, file_type, permissions, link_count, uid, gid, filesize, mtime, filename) - // Expected 7 + 1 (8) values: + 1 cause regex is repeated at 0 - if metadata.len() < 8 { - return Err(()); - } - // Collect metadata - // Get if is directory and if is symlink - let (mut is_dir, is_symlink): (bool, bool) = match metadata.get(1).unwrap().as_str() - { - "-" => (false, false), - "l" => (false, true), - "d" => (true, false), - _ => return Err(()), // Ignore special files - }; - // Check string length (unix pex) - if metadata.get(2).unwrap().as_str().len() < 9 { - return Err(()); - } - - let pex = |range: Range| { - let mut count: u8 = 0; - for (i, c) in metadata.get(2).unwrap().as_str()[range].chars().enumerate() { - match c { - '-' => {} - _ => { - count += match i { - 0 => 4, - 1 => 2, - 2 => 1, - _ => 0, - } - } - } + /// Get FsEntry from symlink + fn get_symlink_entry(wrkdir: &Path, link: Option<&Path>) -> Option> { + match link { + None => None, + Some(p) => { + // Make abs path + let abs_path: PathBuf = match p.is_absolute() { + true => p.to_path_buf(), + false => { + let mut abs = wrkdir.to_path_buf(); + abs.push(p); + abs } - count }; - - // Get unix pex - let unix_pex = (pex(0..3), pex(3..6), pex(6..9)); - - // Parse mtime and convert to SystemTime - let mtime: SystemTime = match parse_lstime( - metadata.get(7).unwrap().as_str(), - "%b %d %Y", - "%b %d %H:%M", - ) { - Ok(t) => t, - Err(_) => SystemTime::UNIX_EPOCH, - }; - // Get uid - let uid: Option = match metadata.get(4).unwrap().as_str().parse::() { - Ok(uid) => Some(uid), - Err(_) => None, - }; - // Get gid - let gid: Option = match metadata.get(5).unwrap().as_str().parse::() { - Ok(gid) => Some(gid), - Err(_) => None, - }; - // Get filesize - let filesize: usize = metadata - .get(6) - .unwrap() - .as_str() - .parse::() - .unwrap_or(0); - // Split filename if required - let (file_name, symlink_path): (String, Option) = match is_symlink { - true => self.get_name_and_link(metadata.get(8).unwrap().as_str()), - false => (String::from(metadata.get(8).unwrap().as_str()), None), - }; - // Check if file_name is '.' or '..' - if file_name.as_str() == "." || file_name.as_str() == ".." { - debug!("File name is {}; ignoring entry", file_name); - return Err(()); - } - // Get symlink - 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; - FsEntry::Directory(FsDirectory { - name: p - .file_name() - .unwrap_or_else(|| std::ffi::OsStr::new("")) - .to_string_lossy() - .to_string(), - abs_path: p.clone(), - last_change_time: mtime, - last_access_time: mtime, - creation_time: mtime, - readonly: false, - symlink: None, - user: uid, - group: gid, - unix_pex: Some(unix_pex), - }) - } - false => FsEntry::File(FsFile { - name: p - .file_name() - .unwrap_or_else(|| std::ffi::OsStr::new("")) - .to_string_lossy() - .to_string(), - abs_path: p.clone(), - last_change_time: mtime, - last_access_time: mtime, - creation_time: mtime, - readonly: false, - symlink: None, - size: filesize, - ftype: p.extension().map(|s| String::from(s.to_string_lossy())), - user: uid, - 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()); - // get extension - let extension: Option = abs_path - .as_path() - .extension() - .map(|s| String::from(s.to_string_lossy())); - // Return - debug!("Follows LIST line '{}' attributes", line); - debug!("Is directory? {}", is_dir); - debug!("Is symlink? {}", is_symlink); - debug!("name: {}", file_name); - debug!("abs_path: {}", abs_path.display()); - debug!("last_change_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S")); - debug!("last_access_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S")); - debug!("creation_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S")); - debug!("symlink: {:?}", symlink); - debug!("user: {:?}", uid); - debug!("group: {:?}", gid); - debug!("unix_pex: {:?}", unix_pex); - debug!("---------------------------------------"); - // Push to entries - Ok(match is_dir { - true => FsEntry::Directory(FsDirectory { - name: file_name, - abs_path, - last_change_time: mtime, - last_access_time: mtime, - creation_time: mtime, - readonly: false, - symlink, - user: uid, - group: gid, - unix_pex: Some(unix_pex), - }), - false => FsEntry::File(FsFile { - name: file_name, - abs_path, - last_change_time: mtime, - last_access_time: mtime, - creation_time: mtime, - size: filesize, - ftype: extension, - readonly: false, - symlink, - user: uid, - group: gid, - unix_pex: Some(unix_pex), - }), - }) + Some(Box::new(FsEntry::File(FsFile { + name: p + .file_name() + .map(|x| x.to_str().unwrap_or("").to_string()) + .unwrap_or_default(), + ftype: abs_path + .extension() + .map(|ext| String::from(ext.to_str().unwrap_or(""))), + size: 0, + last_access_time: UNIX_EPOCH, + last_change_time: UNIX_EPOCH, + creation_time: UNIX_EPOCH, + user: None, + group: None, + readonly: false, + symlink: None, + unix_pex: None, + abs_path, + }))) } - None => Err(()), } } - /// ### parse_dos_list_line + /// ### query_unix_pex /// - /// Try to parse a "LIST" output command line in DOS format. - /// Returns error if syntax is not DOS compliant. - /// DOS syntax has the following syntax: - /// {DATE} {TIME} { | SIZE} {FILENAME} - /// 10-19-20 03:19PM pub - /// 04-08-14 03:09PM 403 readme.txt - fn parse_dos_list_line(&self, path: &Path, line: &str) -> Result { - // Prepare list regex - // NOTE: you won't find this regex on the internet. It seems I'm the only person in the world who needs this - lazy_static! { - static ref DOS_RE: Regex = Regex::new( - r#"^(\d{2}\-\d{2}\-\d{2}\s+\d{2}:\d{2}\s*[AP]M)\s+()?([\d,]*)\s+(.+)$"# - ) - .unwrap(); - } - debug!("Parsing LIST (DOS) line: '{}'", line); - // Apply regex to result - match DOS_RE.captures(line) { - // String matches regex - Some(metadata) => { - // NOTE: metadata fmt: (regex, date_time, is_dir?, file_size?, file_name) - // Expected 4 + 1 (5) values: + 1 cause regex is repeated at 0 - if metadata.len() < 5 { - return Err(()); - } - // Parse date time - let time: SystemTime = - match parse_datetime(metadata.get(1).unwrap().as_str(), "%d-%m-%y %I:%M%p") { - Ok(t) => t, - Err(_) => SystemTime::UNIX_EPOCH, // Don't return error - }; - // Get if is a directory - let is_dir: bool = metadata.get(2).is_some(); - // Get file size - let file_size: usize = match is_dir { - true => 0, // If is directory, filesize is 0 - false => match metadata.get(3) { - // If is file, parse arg 3 - Some(val) => val.as_str().parse::().unwrap_or(0), - None => 0, // Should not happen - }, - }; - // Get file name - let file_name: String = String::from(metadata.get(4).unwrap().as_str()); - // Get absolute path - 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()); - // Get extension - let extension: Option = abs_path - .as_path() - .extension() - .map(|s| String::from(s.to_string_lossy())); - debug!("Follows LIST line '{}' attributes", line); - debug!("Is directory? {}", is_dir); - debug!("name: {}", file_name); - debug!("abs_path: {}", abs_path.display()); - debug!("last_change_time: {}", fmt_time(time, "%Y-%m-%dT%H:%M:%S")); - debug!("last_access_time: {}", fmt_time(time, "%Y-%m-%dT%H:%M:%S")); - debug!("creation_time: {}", fmt_time(time, "%Y-%m-%dT%H:%M:%S")); - debug!("---------------------------------------"); - // Return entry - Ok(match is_dir { - true => FsEntry::Directory(FsDirectory { - name: file_name, - abs_path, - last_change_time: time, - last_access_time: time, - creation_time: time, - readonly: false, - symlink: None, - user: None, - group: None, - unix_pex: None, - }), - false => FsEntry::File(FsFile { - name: file_name, - abs_path, - last_change_time: time, - last_access_time: time, - creation_time: time, - size: file_size, - ftype: extension, - readonly: false, - symlink: None, - user: None, - group: None, - unix_pex: None, - }), - }) - } - None => Err(()), // Invalid syntax - } + /// Returns unix pex in tuple of values + fn query_unix_pex(f: &File) -> (u8, u8, u8) { + ( + Self::pex_to_byte( + f.can_read(UnixPexQuery::Owner), + f.can_write(UnixPexQuery::Owner), + f.can_execute(UnixPexQuery::Owner), + ), + Self::pex_to_byte( + f.can_read(UnixPexQuery::Group), + f.can_write(UnixPexQuery::Group), + f.can_execute(UnixPexQuery::Group), + ), + Self::pex_to_byte( + f.can_read(UnixPexQuery::Others), + f.can_write(UnixPexQuery::Others), + f.can_execute(UnixPexQuery::Others), + ), + ) } - /// ### get_name_and_link + /// ### pex_to_byte /// - /// Returns from a `ls -l` command output file name token, the name of the file and the symbolic link (if there is any) - fn get_name_and_link(&self, token: &str) -> (String, Option) { - let tokens: Vec<&str> = token.split(" -> ").collect(); - let filename: String = String::from(*tokens.get(0).unwrap()); - let symlink: Option = tokens.get(1).map(PathBuf::from); - (filename, symlink) + /// Convert unix permissions to byte value + fn pex_to_byte(read: bool, write: bool, exec: bool) -> u8 { + ((read as u8) << 2) + ((write as u8) << 1) + (exec as u8) } } @@ -473,7 +275,12 @@ impl FileTransfer for FtpFileTransfer { self.stream = Some(stream); info!("Connection successfully established"); // Return OK - Ok(self.stream.as_ref().unwrap().get_welcome_msg()) + Ok(self + .stream + .as_ref() + .unwrap() + .get_welcome_msg() + .map(|x| x.to_string())) } /// ### disconnect @@ -567,22 +374,10 @@ impl FileTransfer for FtpFileTransfer { info!("LIST dir {}", dir.display()); match &mut self.stream { Some(stream) => match stream.list(Some(&dir.as_path().to_string_lossy())) { - Ok(entries) => { - debug!("Got {} lines in LIST result", entries.len()); - // Prepare result - let mut result: Vec = Vec::with_capacity(entries.len()); + Ok(lines) => { + debug!("Got {} lines in LIST result", lines.len()); // Iterate over entries - for entry in entries.iter() { - if let Ok(file) = self.parse_list_line(dir.as_path(), entry) { - result.push(file); - } - } - debug!( - "{} out of {} were valid entries", - result.len(), - entries.len() - ); - Ok(result) + Ok(self.parse_list_lines(path, lines)) } Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::DirStatFailed, @@ -597,13 +392,23 @@ impl FileTransfer for FtpFileTransfer { /// ### mkdir /// - /// Make directory + /// In case the directory already exists, it must return an Error of kind `FileTransferErrorType::DirectoryAlreadyExists` fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> { let dir: PathBuf = Self::resolve(dir); info!("MKDIR {}", dir.display()); match &mut self.stream { Some(stream) => match stream.mkdir(&dir.as_path().to_string_lossy()) { Ok(_) => Ok(()), + Err(FtpError::InvalidResponse(Response { + // Directory already exists + code: FILE_UNAVAILABLE, + body: _, + })) => { + error!("Directory {} already exists", dir.display()); + Err(FileTransferError::new( + FileTransferErrorType::DirectoryAlreadyExists, + )) + } Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::FileCreateDenied, err.to_string(), @@ -791,7 +596,8 @@ impl FileTransfer for FtpFileTransfer { fn recv_file(&mut self, file: &FsFile) -> Result, FileTransferError> { info!("Receiving file {}", file.abs_path.display()); match &mut self.stream { - Some(stream) => match stream.get(&file.abs_path.as_path().to_string_lossy()) { + Some(stream) => match stream.retr_as_stream(&file.abs_path.as_path().to_string_lossy()) + { Ok(reader) => Ok(Box::new(reader)), // NOTE: don't use BufReader here, since already returned by the library Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::NoSuchFileOrDirectory, @@ -837,7 +643,7 @@ impl FileTransfer for FtpFileTransfer { fn on_recv(&mut self, readable: Box) -> Result<(), FileTransferError> { info!("Finalizing get"); match &mut self.stream { - Some(stream) => match stream.finalize_get(readable) { + Some(stream) => match stream.finalize_retr_stream(readable) { Ok(_) => Ok(()), Err(err) => Err(FileTransferError::new_ex( FileTransferErrorType::ProtocolError, @@ -856,7 +662,6 @@ mod tests { use super::*; use crate::utils::file::open_file; - use crate::utils::fmt::fmt_time; #[cfg(feature = "with-containers")] use crate::utils::test_helpers::write_file; use crate::utils::test_helpers::{create_sample_file_entry, make_fsentry}; @@ -902,6 +707,14 @@ mod tests { assert_eq!(ftp.list_dir(&Path::new("/")).unwrap().len(), 0); // Make directory assert!(ftp.mkdir(PathBuf::from("/home").as_path()).is_ok()); + // Remake directory (should report already exists) + assert_eq!( + ftp.mkdir(PathBuf::from("/home").as_path()) + .err() + .unwrap() + .kind(), + FileTransferErrorType::DirectoryAlreadyExists + ); // Make directory (err) assert!(ftp.mkdir(PathBuf::from("/root/pommlar").as_path()).is_err()); // Change directory @@ -957,9 +770,9 @@ mod tests { let dummy: FsEntry = FsEntry::File(FsFile { name: String::from("cucumber.txt"), abs_path: PathBuf::from("/cucumber.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, + last_change_time: UNIX_EPOCH, + last_access_time: UNIX_EPOCH, + creation_time: UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type readonly: true, @@ -1051,12 +864,13 @@ mod tests { let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); // Simple file let file: FsFile = ftp - .parse_list_line( + .parse_list_lines( PathBuf::from("/tmp").as_path(), - "-rw-rw-r-- 1 root dialout 8192 Nov 5 2018 omar.txt", + vec!["-rw-rw-r-- 1 root dialout 8192 Nov 5 2018 omar.txt".to_string()], ) - .ok() + .get(0) .unwrap() + .clone() .unwrap_file(); assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); assert_eq!(file.name, String::from("omar.txt")); @@ -1067,180 +881,22 @@ mod tests { assert_eq!(file.unix_pex.unwrap(), (6, 6, 4)); assert_eq!( file.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) + .duration_since(UNIX_EPOCH) .ok() .unwrap(), Duration::from_secs(1541376000) ); assert_eq!( file.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) + .duration_since(UNIX_EPOCH) .ok() .unwrap(), Duration::from_secs(1541376000) ); assert_eq!( - file.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), + file.creation_time.duration_since(UNIX_EPOCH).ok().unwrap(), Duration::from_secs(1541376000) ); - // Simple file with number as gid, uid - let file: FsFile = ftp - .parse_list_line( - PathBuf::from("/tmp").as_path(), - "-rwxr-xr-x 1 0 9 4096 Nov 5 16:32 omar.txt", - ) - .ok() - .unwrap() - .unwrap_file(); - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 4096); - assert!(file.symlink.is_none()); - assert_eq!(file.user, Some(0)); - assert_eq!(file.group, Some(9)); - assert_eq!(file.unix_pex.unwrap(), (7, 5, 5)); - assert_eq!( - fmt_time(file.last_access_time, "%m %d %M").as_str(), - "11 05 32" - ); - assert_eq!( - fmt_time(file.last_change_time, "%m %d %M").as_str(), - "11 05 32" - ); - assert_eq!( - fmt_time(file.creation_time, "%m %d %M").as_str(), - "11 05 32" - ); - // Directory - let dir: FsDirectory = ftp - .parse_list_line( - PathBuf::from("/tmp").as_path(), - "drwxrwxr-x 1 0 9 4096 Nov 5 2018 docs", - ) - .ok() - .unwrap() - .unwrap_dir(); - assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(dir.name, String::from("docs")); - assert!(dir.symlink.is_none()); - assert_eq!(dir.user, Some(0)); - assert_eq!(dir.group, Some(9)); - assert_eq!(dir.unix_pex.unwrap(), (7, 7, 5)); - assert_eq!( - dir.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - dir.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!( - dir.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1541376000) - ); - assert_eq!(dir.readonly, false); - // Error - assert!(ftp - .parse_list_line( - PathBuf::from("/").as_path(), - "drwxrwxr-x 1 0 9 Nov 5 2018 docs" - ) - .is_err()); - } - - #[test] - fn test_filetransfer_ftp_parse_list_line_dos() { - let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); - // Simple file - let file: FsFile = ftp - .parse_list_line( - PathBuf::from("/tmp").as_path(), - "04-08-14 03:09PM 8192 omar.txt", - ) - .ok() - .unwrap() - .unwrap_file(); - assert_eq!(file.abs_path, PathBuf::from("/tmp/omar.txt")); - assert_eq!(file.name, String::from("omar.txt")); - assert_eq!(file.size, 8192); - assert!(file.symlink.is_none()); - assert_eq!(file.user, None); - assert_eq!(file.group, None); - assert_eq!(file.unix_pex, None); - assert_eq!( - file.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - file.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - file.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - // Directory - let dir: FsDirectory = ftp - .parse_list_line( - PathBuf::from("/tmp").as_path(), - "04-08-14 03:09PM docs", - ) - .ok() - .unwrap() - .unwrap_dir(); - assert_eq!(dir.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(dir.name, String::from("docs")); - assert!(dir.symlink.is_none()); - assert_eq!(dir.user, None); - assert_eq!(dir.group, None); - assert_eq!(dir.unix_pex, None); - assert_eq!( - dir.last_access_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - dir.last_change_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!( - dir.creation_time - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .unwrap(), - Duration::from_secs(1407164940) - ); - assert_eq!(dir.readonly, false); - // Error - assert!(ftp - .parse_list_line(PathBuf::from("/").as_path(), "04-08-14 omar.txt") - .is_err()); } #[test] @@ -1265,27 +921,14 @@ mod tests { assert!(ftp.disconnect().is_ok()); } - #[test] - fn test_filetransfer_ftp_get_name_and_link() { - let client: FtpFileTransfer = FtpFileTransfer::new(false); - assert_eq!( - client.get_name_and_link("Cargo.toml"), - (String::from("Cargo.toml"), None) - ); - assert_eq!( - client.get_name_and_link("Cargo -> Cargo.toml"), - (String::from("Cargo"), Some(PathBuf::from("Cargo.toml"))) - ); - } - #[test] fn test_filetransfer_ftp_uninitialized() { let file: FsFile = FsFile { name: String::from("omar.txt"), abs_path: PathBuf::from("/omar.txt"), - last_change_time: SystemTime::UNIX_EPOCH, - last_access_time: SystemTime::UNIX_EPOCH, - creation_time: SystemTime::UNIX_EPOCH, + last_change_time: UNIX_EPOCH, + last_access_time: UNIX_EPOCH, + creation_time: UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type readonly: true, diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 79d4a7c..39d10c0 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -182,7 +182,7 @@ pub trait FileTransfer { /// ### mkdir /// /// Make directory - /// It MUSTN'T return error in case the directory already exists + /// In case the directory already exists, it must return an Error of kind `FileTransferErrorType::DirectoryAlreadyExists` fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError>; /// ### remove diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index e08c0d0..8ff89a7 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -643,7 +643,7 @@ impl FileTransfer for ScpFileTransfer { /// ### mkdir /// /// Make directory - /// It MUSTN'T return error in case the directory already exists + /// In case the directory already exists, it must return an Error of kind `FileTransferErrorType::DirectoryAlreadyExists` fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> { match self.is_connected() { true => { @@ -651,7 +651,7 @@ impl FileTransfer for ScpFileTransfer { info!("Making directory {}", dir.display()); let p: PathBuf = self.wrkdir.clone(); // If directory already exists, return Err - if let Ok(_) = self.stat(dir.as_path()) { + if self.stat(dir.as_path()).is_ok() { error!("Directory {} already exists", dir.display()); return Err(FileTransferError::new( FileTransferErrorType::DirectoryAlreadyExists, @@ -1026,6 +1026,15 @@ mod tests { assert!(client.list_dir(&Path::new("/config")).unwrap().len() >= 4); // Make directory assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); + // Remake directory (should report already exists) + assert_eq!( + client + .mkdir(PathBuf::from("/tmp/omar").as_path()) + .err() + .unwrap() + .kind(), + FileTransferErrorType::DirectoryAlreadyExists + ); // Make directory (err) assert!(client .mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path()) diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index b067ee3..64050b3 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -554,13 +554,14 @@ impl FileTransfer for SftpFileTransfer { /// ### mkdir /// /// Make directory + /// In case the directory already exists, it must return an Error of kind `FileTransferErrorType::DirectoryAlreadyExists` fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> { match self.sftp.as_ref() { Some(sftp) => { // Make directory let path: PathBuf = self.get_abs_path(PathBuf::from(dir).as_path()); // If directory already exists, return Err - if let Ok(_) = sftp.stat(path.as_path()) { + if sftp.stat(path.as_path()).is_ok() { error!("Directory {} already exists", path.display()); return Err(FileTransferError::new( FileTransferErrorType::DirectoryAlreadyExists, @@ -846,6 +847,15 @@ mod tests { assert!(client.list_dir(&Path::new("/config")).unwrap().len() >= 4); // Make directory assert!(client.mkdir(PathBuf::from("/tmp/omar").as_path()).is_ok()); + // Remake directory (should report already exists) + assert_eq!( + client + .mkdir(PathBuf::from("/tmp/omar").as_path()) + .err() + .unwrap() + .kind(), + FileTransferErrorType::DirectoryAlreadyExists + ); // Make directory (err) assert!(client .mkdir(PathBuf::from("/root/aaaaa/pommlar").as_path()) diff --git a/src/lib.rs b/src/lib.rs index 48bed11..b1f2840 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,6 @@ extern crate content_inspector; extern crate crossterm; extern crate dirs; extern crate edit; -extern crate ftp4; extern crate hostname; #[cfg(feature = "with-keyring")] extern crate keyring; @@ -54,6 +53,7 @@ extern crate path_slash; extern crate rand; extern crate regex; extern crate ssh2; +extern crate suppaftp; extern crate tempfile; extern crate textwrap; extern crate tui_realm_stdlib; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 1cb15c5..7359b44 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -202,6 +202,7 @@ pub fn parse_lstime(tm: &str, fmt_year: &str, fmt_hours: &str) -> Result Result { match NaiveDateTime::parse_from_str(tm, fmt) { Ok(dt) => { From f6011543c050441e80595bf011c3b904fd3ba833 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 11:18:11 +0200 Subject: [PATCH 08/20] Fixed ui test units --- src/ui/components/logbox.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/logbox.rs b/src/ui/components/logbox.rs index 69f5117..41ac7d1 100644 --- a/src/ui/components/logbox.rs +++ b/src/ui/components/logbox.rs @@ -337,7 +337,7 @@ mod tests { .visible() .with_borders(Borders::ALL, BorderType::Double, Color::Red) .with_background(Color::Blue) - .with_title("log", Alignment::Left) + .with_title("Log", Alignment::Left) .with_log( TableBuilder::default() .add_col(TextSpan::from("12:29")) From fc3803991a03780c134f2cbcbc4b0fa0d47f198c Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 11:38:41 +0200 Subject: [PATCH 09/20] Fixed scp transfer non returning DirectoryAlreadyExists --- src/filetransfer/scp_transfer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 8ff89a7..d98b3e3 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -651,7 +651,9 @@ impl FileTransfer for ScpFileTransfer { info!("Making directory {}", dir.display()); let p: PathBuf = self.wrkdir.clone(); // If directory already exists, return Err - if self.stat(dir.as_path()).is_ok() { + let mut dir_stat_path: PathBuf = dir.clone(); + dir_stat_path.push("./"); + if self.stat(dir_stat_path.as_path()).is_ok() { error!("Directory {} already exists", dir.display()); return Err(FileTransferError::new( FileTransferErrorType::DirectoryAlreadyExists, From 7713c6c21db327a61ef929d9149640c5ccf59ee1 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 12:20:25 +0200 Subject: [PATCH 10/20] suppaftp 4.1.2 --- CHANGELOG.md | 2 +- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/filetransfer/ftp_transfer.rs | 22 +++++++++++----------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc4276..716e519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Released on ?? - Updated `crossterm` to `0.20` - Updated `open` to `2.0.1` - Added `tui-realm-stdlib 0.6.0` - - Replaced `ftp4` with `suppaftp 4.1.1` + - Replaced `ftp4` with `suppaftp 4.1.2` - Updated `tui-realm` to `0.6.0` ## 0.6.0 diff --git a/Cargo.lock b/Cargo.lock index ea2f5b1..0684892 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1324,9 +1324,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "suppaftp" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d8310cb2dcc312f9e941d35453a1b2cb654186f4ec60b02e2d778a221c3002b" +checksum = "29a4d861acfdc117c6d373c3b743c534dbbbb2d782e7646b27439a7c5282ad6a" dependencies = [ "chrono", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index ec78301..7066e40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ rpassword = "5.0.1" serde = { version = "^1.0.0", features = [ "derive" ] } simplelog = "0.10.0" ssh2 = "0.9.0" -suppaftp = { version = "4.1.1", features = [ "secure" ] } +suppaftp = { version = "4.1.2", features = [ "secure" ] } tempfile = "3.1.0" textwrap = "0.14.2" thiserror = "^1.0.0" diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 32d96e3..cd3e8e5 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -36,7 +36,7 @@ use std::path::{Path, PathBuf}; use std::time::UNIX_EPOCH; use suppaftp::native_tls::TlsConnector; use suppaftp::{ - list::{File, UnixPexQuery}, + list::{File, PosixPexQuery}, status::FILE_UNAVAILABLE, types::{FileType, Response}, FtpError, FtpStream, @@ -164,19 +164,19 @@ impl FtpFileTransfer { fn query_unix_pex(f: &File) -> (u8, u8, u8) { ( Self::pex_to_byte( - f.can_read(UnixPexQuery::Owner), - f.can_write(UnixPexQuery::Owner), - f.can_execute(UnixPexQuery::Owner), + f.can_read(PosixPexQuery::Owner), + f.can_write(PosixPexQuery::Owner), + f.can_execute(PosixPexQuery::Owner), ), Self::pex_to_byte( - f.can_read(UnixPexQuery::Group), - f.can_write(UnixPexQuery::Group), - f.can_execute(UnixPexQuery::Group), + f.can_read(PosixPexQuery::Group), + f.can_write(PosixPexQuery::Group), + f.can_execute(PosixPexQuery::Group), ), Self::pex_to_byte( - f.can_read(UnixPexQuery::Others), - f.can_write(UnixPexQuery::Others), - f.can_execute(UnixPexQuery::Others), + f.can_read(PosixPexQuery::Others), + f.can_write(PosixPexQuery::Others), + f.can_execute(PosixPexQuery::Others), ), ) } @@ -399,7 +399,7 @@ impl FileTransfer for FtpFileTransfer { match &mut self.stream { Some(stream) => match stream.mkdir(&dir.as_path().to_string_lossy()) { Ok(_) => Ok(()), - Err(FtpError::InvalidResponse(Response { + Err(FtpError::UnexpectedResponse(Response { // Directory already exists code: FILE_UNAVAILABLE, body: _, From b7b765c16e792906205c2778731d7c2313405d8b Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 14:56:27 +0200 Subject: [PATCH 11/20] Fixed: When copying files with tricky copy, the upper progress bar shows no text --- src/ui/activities/filetransfer/actions/copy.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index 1d28fe1..3970603 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -144,6 +144,8 @@ impl FileTransferActivity { /// /// Tricky copy will be used whenever copy command is not available on remote host fn tricky_copy(&mut self, entry: FsEntry, dest: &Path) { + // NOTE: VERY IMPORTANT; wait block must be umounted or something really bad will happen + self.umount_wait(); // match entry match entry { FsEntry::File(entry) => { From a189a4c7547657d6912f2e890554ab5f495318c7 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 14:59:47 +0200 Subject: [PATCH 12/20] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716e519..3b50fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Released on ?? - Added new `Directory already exists` variant for file transfer errors - Bugfix: - Fixed [Issue 58](https://github.com/veeso/termscp/issues/58):When uploading a directory, create directory only if it doesn't exist + - Fixed [Issue 59](https://github.com/veeso/termscp/issues/59): When copying files with tricky copy, the upper progress bar shows no text - Dependencies: - Updated `bitflags` to `1.3.2` - Updated `bytesize` to `1.1.0` From 32ab0267fbe73a97cc417419398b8bb5215d60db Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 17:18:27 +0200 Subject: [PATCH 13/20] Removed unused readonly attribute for FsEntry --- src/filetransfer/ftp_transfer.rs | 21 ++++++++------------- src/filetransfer/scp_transfer.rs | 23 ++++++++--------------- src/filetransfer/sftp_transfer.rs | 20 ++++++++------------ src/fs/explorer/formatter.rs | 13 ------------- src/fs/explorer/mod.rs | 9 ++------- src/fs/mod.rs | 16 +--------------- src/host/mod.rs | 5 ----- src/utils/test_helpers.rs | 9 +++------ 8 files changed, 30 insertions(+), 86 deletions(-) diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index cd3e8e5..dc9b108 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -92,7 +92,6 @@ impl FtpFileTransfer { last_access_time: x.modified(), last_change_time: x.modified(), creation_time: x.modified(), - readonly: false, symlink: None, user: x.uid(), group: x.gid(), @@ -107,7 +106,6 @@ impl FtpFileTransfer { last_access_time: x.modified(), last_change_time: x.modified(), creation_time: x.modified(), - readonly: false, user: x.uid(), group: x.gid(), symlink: Self::get_symlink_entry(path, x.symlink()), @@ -149,7 +147,6 @@ impl FtpFileTransfer { creation_time: UNIX_EPOCH, user: None, group: None, - readonly: false, symlink: None, unix_pex: None, abs_path, @@ -775,11 +772,10 @@ mod tests { creation_time: UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only }); assert!(ftp .rename(&dummy, PathBuf::from("/a/b/c").as_path()) @@ -931,11 +927,10 @@ mod tests { creation_time: UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only }; let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); assert!(ftp.change_dir(Path::new("/tmp")).is_err()); diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index d98b3e3..478038a 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -218,7 +218,6 @@ impl ScpFileTransfer { last_change_time: mtime, last_access_time: mtime, creation_time: mtime, - readonly: false, symlink, user: uid, group: gid, @@ -232,7 +231,6 @@ impl ScpFileTransfer { creation_time: mtime, size: filesize, ftype: extension, - readonly: false, symlink, user: uid, group: gid, @@ -1125,11 +1123,10 @@ mod tests { creation_time: SystemTime::UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only }); assert!(client .rename(&dummy, PathBuf::from("/a/b/c").as_path()) @@ -1244,7 +1241,6 @@ mod tests { assert_eq!(entry.abs_path, PathBuf::from("/tmp/Cargo.toml")); assert_eq!(entry.unix_pex.unwrap(), (6, 4, 4)); assert_eq!(entry.size, 2056); - assert_eq!(entry.readonly, false); assert_eq!(entry.ftype.unwrap().as_str(), "toml"); assert!(entry.symlink.is_none()); // File (year) @@ -1260,7 +1256,6 @@ mod tests { assert_eq!(entry.abs_path, PathBuf::from("/tmp/CODE_OF_CONDUCT.md")); assert_eq!(entry.unix_pex.unwrap(), (6, 6, 6)); assert_eq!(entry.size, 3368); - assert_eq!(entry.readonly, false); assert_eq!(entry.ftype.unwrap().as_str(), "md"); assert!(entry.symlink.is_none()); // Directory @@ -1275,7 +1270,6 @@ mod tests { assert_eq!(entry.name.as_str(), "docs"); assert_eq!(entry.abs_path, PathBuf::from("/tmp/docs")); assert_eq!(entry.unix_pex.unwrap(), (7, 5, 5)); - assert_eq!(entry.readonly, false); assert!(entry.symlink.is_none()); // Short metadata assert!(client @@ -1323,11 +1317,10 @@ mod tests { creation_time: SystemTime::UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only }; let mut scp: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(scp.change_dir(Path::new("/tmp")).is_err()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 64050b3..398cfc6 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -178,7 +178,6 @@ impl SftpFileTransfer { last_change_time: mtime, last_access_time: atime, creation_time: SystemTime::UNIX_EPOCH, - readonly: false, symlink, user: uid, group: gid, @@ -192,7 +191,6 @@ impl SftpFileTransfer { last_change_time: mtime, last_access_time: atime, creation_time: SystemTime::UNIX_EPOCH, - readonly: false, symlink, user: uid, group: gid, @@ -923,11 +921,10 @@ mod tests { creation_time: SystemTime::UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only }); assert!(client .rename(&dummy, PathBuf::from("/a/b/c").as_path()) @@ -1072,11 +1069,10 @@ mod tests { creation_time: SystemTime::UNIX_EPOCH, size: 0, ftype: Some(String::from("txt")), // File type - readonly: true, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only }; let mut sftp: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(sftp.change_dir(Path::new("/tmp")).is_err()); diff --git a/src/fs/explorer/formatter.rs b/src/fs/explorer/formatter.rs index 54ba14d..f630b21 100644 --- a/src/fs/explorer/formatter.rs +++ b/src/fs/explorer/formatter.rs @@ -552,7 +552,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -593,7 +592,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -624,7 +622,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -655,7 +652,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -686,7 +682,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: None, // UNIX only @@ -723,7 +718,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -752,7 +746,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: None, // UNIX only group: Some(0), // UNIX only @@ -789,7 +782,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: None, // UNIX only @@ -802,7 +794,6 @@ mod tests { last_change_time: t, last_access_time: t, creation_time: t, - readonly: false, symlink: Some(Box::new(pointer)), // UNIX only user: None, // UNIX only group: None, // UNIX only @@ -821,7 +812,6 @@ mod tests { last_change_time: t, last_access_time: t, creation_time: t, - readonly: false, symlink: None, // UNIX only user: None, // UNIX only group: None, // UNIX only @@ -841,7 +831,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: None, // UNIX only @@ -855,7 +844,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: Some(Box::new(pointer)), // UNIX only user: None, // UNIX only @@ -876,7 +864,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: None, // UNIX only diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 27b74f8..5d98603 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -586,7 +586,6 @@ mod tests { last_access_time: t, creation_time: t, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -670,8 +669,7 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 64, - ftype: None, // File type - readonly: false, + ftype: None, // File type symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -683,7 +681,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -702,8 +699,7 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: size, - ftype: None, // File type - readonly: false, + ftype: None, // File type symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -715,7 +711,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 07e06a9..cb21fdd 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -52,7 +52,6 @@ pub struct FsDirectory { pub last_change_time: SystemTime, pub last_access_time: SystemTime, pub creation_time: SystemTime, - pub readonly: bool, pub symlink: Option>, // UNIX only pub user: Option, // UNIX only pub group: Option, // UNIX only @@ -71,8 +70,7 @@ pub struct FsFile { pub last_access_time: SystemTime, pub creation_time: SystemTime, pub size: usize, - pub ftype: Option, // File type - pub readonly: bool, + pub ftype: Option, // File type pub symlink: Option>, // UNIX only pub user: Option, // UNIX only pub group: Option, // UNIX only @@ -264,7 +262,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -296,7 +293,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -330,7 +326,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -350,7 +345,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -369,7 +363,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -384,7 +377,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -398,7 +390,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -418,7 +409,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8192, - readonly: false, ftype: Some(String::from("txt")), symlink: None, // UNIX only user: Some(0), // UNIX only @@ -437,7 +427,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -457,7 +446,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -469,7 +457,6 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - readonly: false, symlink: Some(Box::new(entry_target)), user: Some(0), group: Some(0), @@ -482,7 +469,6 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 8, - readonly: false, ftype: None, symlink: Some(Box::new(entry_child)), user: Some(0), diff --git a/src/host/mod.rs b/src/host/mod.rs index f9913de..9eb434a 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -461,7 +461,6 @@ impl Localhost { last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), - readonly: attr.permissions().readonly(), symlink: match fs::read_link(path.as_path()) { Ok(p) => match self.stat(p.as_path()) { Ok(entry) => Some(Box::new(entry)), @@ -484,7 +483,6 @@ impl Localhost { last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), - readonly: attr.permissions().readonly(), size: attr.len() as usize, ftype: extension, symlink: match fs::read_link(path.as_path()) { @@ -506,7 +504,6 @@ impl Localhost { /// /// Stat file and create a FsEntry #[cfg(target_os = "windows")] - #[cfg(not(tarpaulin_include))] pub fn stat(&self, path: &Path) -> Result { let path: PathBuf = self.to_abs_path(path); info!("Stating file {}", path.display()); @@ -530,7 +527,6 @@ impl Localhost { last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), - readonly: attr.permissions().readonly(), symlink: match fs::read_link(path.as_path()) { Ok(p) => match self.stat(p.as_path()) { Ok(entry) => Some(Box::new(entry)), @@ -554,7 +550,6 @@ impl Localhost { last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), - readonly: attr.permissions().readonly(), size: attr.len() as usize, ftype: extension, symlink: match fs::read_link(path.as_path()) { diff --git a/src/utils/test_helpers.rs b/src/utils/test_helpers.rs index 45239c8..8706900 100644 --- a/src/utils/test_helpers.rs +++ b/src/utils/test_helpers.rs @@ -53,8 +53,7 @@ pub fn create_sample_file_entry() -> (FsFile, NamedTempFile) { last_access_time: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, size: 127, - ftype: None, // File type - readonly: false, + ftype: None, // File type symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -162,7 +161,6 @@ pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry { last_change_time: SystemTime::UNIX_EPOCH, last_access_time: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, - readonly: false, symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -175,8 +173,7 @@ pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry { last_access_time: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, size: 127, - ftype: None, // File type - readonly: false, + ftype: None, // File type symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only @@ -200,7 +197,7 @@ mod test { #[test] fn test_utils_test_helpers_sample_file() { let (file, _) = create_sample_file_entry(); - assert_eq!(file.readonly, false); + assert!(file.symlink.is_none()); } #[test] From 92b081c076a08c45749835b481ec8a698c5d5c75 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 17:52:55 +0200 Subject: [PATCH 14/20] Replaced u8 pex with UnixPex struct --- src/filetransfer/ftp_transfer.rs | 26 ++- src/filetransfer/scp_transfer.rs | 33 +++- src/filetransfer/sftp_transfer.rs | 16 +- src/fs/explorer/formatter.rs | 58 ++++--- src/fs/explorer/mod.rs | 46 ++--- src/fs/mod.rs | 199 ++++++++++++++++------ src/host/mod.rs | 10 +- src/ui/activities/filetransfer/session.rs | 18 +- src/utils/fmt.rs | 79 +++------ src/utils/test_helpers.rs | 30 ++-- 10 files changed, 296 insertions(+), 219 deletions(-) diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index dc9b108..2530abd 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -26,7 +26,7 @@ * SOFTWARE. */ use super::{FileTransfer, FileTransferError, FileTransferErrorType}; -use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; use crate::utils::fmt::shadow_password; // Includes @@ -158,32 +158,25 @@ impl FtpFileTransfer { /// ### query_unix_pex /// /// Returns unix pex in tuple of values - fn query_unix_pex(f: &File) -> (u8, u8, u8) { + fn query_unix_pex(f: &File) -> (UnixPex, UnixPex, UnixPex) { ( - Self::pex_to_byte( + UnixPex::new( f.can_read(PosixPexQuery::Owner), f.can_write(PosixPexQuery::Owner), f.can_execute(PosixPexQuery::Owner), ), - Self::pex_to_byte( + UnixPex::new( f.can_read(PosixPexQuery::Group), f.can_write(PosixPexQuery::Group), f.can_execute(PosixPexQuery::Group), ), - Self::pex_to_byte( + UnixPex::new( f.can_read(PosixPexQuery::Others), f.can_write(PosixPexQuery::Others), f.can_execute(PosixPexQuery::Others), ), ) } - - /// ### pex_to_byte - /// - /// Convert unix permissions to byte value - fn pex_to_byte(read: bool, write: bool, exec: bool) -> u8 { - ((read as u8) << 2) + ((write as u8) << 1) + (exec as u8) - } } impl FileTransfer for FtpFileTransfer { @@ -775,7 +768,7 @@ mod tests { symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert!(ftp .rename(&dummy, PathBuf::from("/a/b/c").as_path()) @@ -874,7 +867,10 @@ mod tests { assert!(file.symlink.is_none()); assert_eq!(file.user, None); assert_eq!(file.group, None); - assert_eq!(file.unix_pex.unwrap(), (6, 6, 4)); + assert_eq!( + file.unix_pex.unwrap(), + (UnixPex::from(6), UnixPex::from(6), UnixPex::from(4)) + ); assert_eq!( file.last_access_time .duration_since(UNIX_EPOCH) @@ -930,7 +926,7 @@ mod tests { symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }; let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false); assert!(ftp.change_dir(Path::new("/tmp")).is_err()); diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 478038a..86df810 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -27,7 +27,7 @@ */ // Locals use super::{FileTransfer, FileTransferError, FileTransferErrorType}; -use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; use crate::system::sshkey_storage::SshKeyStorage; use crate::utils::fmt::{fmt_time, shadow_password}; use crate::utils::parser::parse_lstime; @@ -128,7 +128,11 @@ impl ScpFileTransfer { }; // Get unix pex - let unix_pex = (pex(0..3), pex(3..6), pex(6..9)); + let unix_pex = ( + UnixPex::from(pex(0..3)), + UnixPex::from(pex(3..6)), + UnixPex::from(pex(6..9)), + ); // Parse mtime and convert to SystemTime let mtime: SystemTime = match parse_lstime( @@ -873,7 +877,11 @@ impl FileTransfer for ScpFileTransfer { // Calculate file mode let mode: i32 = match local.unix_pex { None => 0o644, - Some((u, g, o)) => ((u as i32) << 6) + ((g as i32) << 3) + (o as i32), + Some((u, g, o)) => { + ((u.as_byte() as i32) << 6) + + ((g.as_byte() as i32) << 3) + + (o.as_byte() as i32) + } }; // Calculate mtime, atime let times: (u64, u64) = { @@ -1126,7 +1134,7 @@ mod tests { symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert!(client .rename(&dummy, PathBuf::from("/a/b/c").as_path()) @@ -1239,7 +1247,10 @@ mod tests { .unwrap_file(); assert_eq!(entry.name.as_str(), "Cargo.toml"); assert_eq!(entry.abs_path, PathBuf::from("/tmp/Cargo.toml")); - assert_eq!(entry.unix_pex.unwrap(), (6, 4, 4)); + assert_eq!( + entry.unix_pex.unwrap(), + (UnixPex::from(6), UnixPex::from(4), UnixPex::from(4)) + ); assert_eq!(entry.size, 2056); assert_eq!(entry.ftype.unwrap().as_str(), "toml"); assert!(entry.symlink.is_none()); @@ -1254,7 +1265,10 @@ mod tests { .unwrap_file(); assert_eq!(entry.name.as_str(), "CODE_OF_CONDUCT.md"); assert_eq!(entry.abs_path, PathBuf::from("/tmp/CODE_OF_CONDUCT.md")); - assert_eq!(entry.unix_pex.unwrap(), (6, 6, 6)); + assert_eq!( + entry.unix_pex.unwrap(), + (UnixPex::from(6), UnixPex::from(6), UnixPex::from(6)) + ); assert_eq!(entry.size, 3368); assert_eq!(entry.ftype.unwrap().as_str(), "md"); assert!(entry.symlink.is_none()); @@ -1269,7 +1283,10 @@ mod tests { .unwrap_dir(); assert_eq!(entry.name.as_str(), "docs"); assert_eq!(entry.abs_path, PathBuf::from("/tmp/docs")); - assert_eq!(entry.unix_pex.unwrap(), (7, 5, 5)); + assert_eq!( + entry.unix_pex.unwrap(), + (UnixPex::from(7), UnixPex::from(5), UnixPex::from(5)) + ); assert!(entry.symlink.is_none()); // Short metadata assert!(client @@ -1320,7 +1337,7 @@ mod tests { symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }; let mut scp: ScpFileTransfer = ScpFileTransfer::new(SshKeyStorage::empty()); assert!(scp.change_dir(Path::new("/tmp")).is_err()); diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 398cfc6..00c50e0 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -27,7 +27,7 @@ */ // Locals use super::{FileTransfer, FileTransferError, FileTransferErrorType}; -use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; use crate::system::sshkey_storage::SshKeyStorage; use crate::utils::fmt::{fmt_time, shadow_password}; @@ -126,11 +126,11 @@ impl SftpFileTransfer { .map(|ext| String::from(ext.to_str().unwrap_or(""))); let uid: Option = metadata.uid; let gid: Option = metadata.gid; - let pex: Option<(u8, u8, u8)> = metadata.perm.map(|x| { + let pex: Option<(UnixPex, UnixPex, UnixPex)> = metadata.perm.map(|x| { ( - ((x >> 6) & 0x7) as u8, - ((x >> 3) & 0x7) as u8, - (x & 0x7) as u8, + UnixPex::from(((x >> 6) & 0x7) as u8), + UnixPex::from(((x >> 3) & 0x7) as u8), + UnixPex::from((x & 0x7) as u8), ) }); let size: u64 = metadata.size.unwrap_or(0); @@ -720,7 +720,7 @@ impl FileTransfer for SftpFileTransfer { // Calculate file mode let mode: i32 = match local.unix_pex { None => 0o644, - Some((u, g, o)) => ((u as i32) << 6) + ((g as i32) << 3) + (o as i32), + Some((u, g, o)) => ((u.as_byte() as i32) << 6) + ((g.as_byte() as i32) << 3) + (o.as_byte() as i32), }; debug!("File mode {:?}", mode); match sftp.open_mode( @@ -924,7 +924,7 @@ mod tests { symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert!(client .rename(&dummy, PathBuf::from("/a/b/c").as_path()) @@ -1072,7 +1072,7 @@ mod tests { symlink: None, // UNIX only user: Some(0), // UNIX only group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }; let mut sftp: SftpFileTransfer = SftpFileTransfer::new(SshKeyStorage::empty()); assert!(sftp.change_dir(Path::new("/tmp")).is_err()); diff --git a/src/fs/explorer/formatter.rs b/src/fs/explorer/formatter.rs index f630b21..006da84 100644 --- a/src/fs/explorer/formatter.rs +++ b/src/fs/explorer/formatter.rs @@ -354,7 +354,9 @@ impl Formatter { pex.push(file_type); match fsentry.get_unix_pex() { None => pex.push_str("?????????"), - Some((owner, group, others)) => pex.push_str(fmt_pex(owner, group, others).as_str()), + Some((owner, group, others)) => pex.push_str( + format!("{}{}{}", fmt_pex(owner), fmt_pex(group), fmt_pex(others)).as_str(), + ), } // Add to cur str, prefix and the key value format!("{}{}{:10}", cur_str, prefix, pex) @@ -533,7 +535,7 @@ impl Formatter { mod tests { use super::*; - use crate::fs::{FsDirectory, FsFile}; + use crate::fs::{FsDirectory, FsFile, UnixPex}; use pretty_assertions::assert_eq; use std::path::PathBuf; @@ -553,10 +555,10 @@ mod tests { creation_time: t_now, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); let prefix: String = String::from("h"); let mut callchain: CallChainBlock = CallChainBlock::new(dummy_fmt, prefix, None, None); @@ -593,10 +595,10 @@ mod tests { creation_time: t, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); #[cfg(target_family = "unix")] assert_eq!( @@ -623,10 +625,10 @@ mod tests { creation_time: t, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); #[cfg(target_family = "unix")] assert_eq!( @@ -718,10 +720,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); #[cfg(target_family = "unix")] assert_eq!( @@ -797,7 +799,7 @@ mod tests { symlink: Some(Box::new(pointer)), // UNIX only user: None, // UNIX only group: None, // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); assert_eq!(formatter.fmt(&entry), format!( "projects/ -> project.info 0 0 lrwxr-xr-x {} {} {}", @@ -812,10 +814,10 @@ mod tests { last_change_time: t, last_access_time: t, creation_time: t, - symlink: None, // UNIX only - user: None, // UNIX only - group: None, // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: None, // UNIX only + group: None, // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); assert_eq!(formatter.fmt(&entry), format!( "projects/ 0 0 drwxr-xr-x {} {} {}", @@ -848,7 +850,7 @@ mod tests { symlink: Some(Box::new(pointer)), // UNIX only user: None, // UNIX only group: None, // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert_eq!(formatter.fmt(&entry), format!( "bar.txt -> project.info 0 0 lrw-r--r-- 8.2 KB {} {} {}", @@ -865,10 +867,10 @@ mod tests { creation_time: t, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: None, // UNIX only - group: None, // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: None, // UNIX only + group: None, // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert_eq!(formatter.fmt(&entry), format!( "bar.txt 0 0 -rw-r--r-- 8.2 KB {} {} {}", diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 5d98603..cb5436f 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -363,7 +363,7 @@ impl FromStr for GroupDirs { mod tests { use super::*; - use crate::fs::{FsDirectory, FsFile}; + use crate::fs::{FsDirectory, FsFile, UnixPex}; use crate::utils::fmt::fmt_time; use pretty_assertions::assert_eq; @@ -587,10 +587,10 @@ mod tests { creation_time: t, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); #[cfg(target_family = "unix")] assert_eq!( @@ -669,11 +669,11 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: 64, - ftype: None, // File type - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + ftype: None, // File type + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }), true => FsEntry::Directory(FsDirectory { name: name.to_string(), @@ -681,10 +681,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }), } } @@ -699,11 +699,11 @@ mod tests { last_access_time: t_now, creation_time: t_now, size: size, - ftype: None, // File type - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + ftype: None, // File type + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }), true => FsEntry::Directory(FsDirectory { name: name.to_string(), @@ -711,10 +711,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }), } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index cb21fdd..d04d3f5 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -52,10 +52,10 @@ pub struct FsDirectory { pub last_change_time: SystemTime, pub last_access_time: SystemTime, pub creation_time: SystemTime, - pub symlink: Option>, // UNIX only - pub user: Option, // UNIX only - pub group: Option, // UNIX only - pub unix_pex: Option<(u8, u8, u8)>, // UNIX only + pub symlink: Option>, // UNIX only + pub user: Option, // UNIX only + pub group: Option, // UNIX only + pub unix_pex: Option<(UnixPex, UnixPex, UnixPex)>, // UNIX only } /// ### FsFile @@ -70,11 +70,72 @@ pub struct FsFile { pub last_access_time: SystemTime, pub creation_time: SystemTime, pub size: usize, - pub ftype: Option, // File type - pub symlink: Option>, // UNIX only - pub user: Option, // UNIX only - pub group: Option, // UNIX only - pub unix_pex: Option<(u8, u8, u8)>, // UNIX only + pub ftype: Option, // File type + pub symlink: Option>, // UNIX only + pub user: Option, // UNIX only + pub group: Option, // UNIX only + pub unix_pex: Option<(UnixPex, UnixPex, UnixPex)>, // UNIX only +} + +/// ## UnixPex +/// +/// Describes the permissions on POSIX system. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct UnixPex { + read: bool, + write: bool, + execute: bool, +} + +impl UnixPex { + /// ### new + /// + /// Instantiates a new `UnixPex` + pub fn new(read: bool, write: bool, execute: bool) -> Self { + Self { + read, + write, + execute, + } + } + + /// ### can_read + /// + /// Returns whether user can read + pub fn can_read(&self) -> bool { + self.read + } + + /// ### can_write + /// + /// Returns whether user can write + pub fn can_write(&self) -> bool { + self.write + } + + /// ### can_execute + /// + /// Returns whether user can execute + pub fn can_execute(&self) -> bool { + self.execute + } + + /// ### as_byte + /// + /// Convert permission to byte as on POSIX systems + pub fn as_byte(&self) -> u8 { + ((self.read as u8) << 2) + ((self.write as u8) << 1) + (self.execute as u8) + } +} + +impl From for UnixPex { + fn from(bits: u8) -> Self { + Self { + read: ((bits >> 2) & 0x01) != 0, + write: ((bits >> 1) & 0x01) != 0, + execute: (bits & 0x01) != 0, + } + } } impl FsEntry { @@ -171,7 +232,7 @@ impl FsEntry { /// ### get_unix_pex /// /// Get unix pex from `FsEntry` - pub fn get_unix_pex(&self) -> Option<(u8, u8, u8)> { + pub fn get_unix_pex(&self) -> Option<(UnixPex, UnixPex, UnixPex)> { match self { FsEntry::Directory(dir) => dir.unix_pex, FsEntry::File(file) => file.unix_pex, @@ -262,10 +323,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); assert_eq!(entry.get_abs_path(), PathBuf::from("/foo")); assert_eq!(entry.get_name(), String::from("foo")); @@ -279,7 +340,10 @@ mod tests { assert_eq!(entry.is_symlink(), false); assert_eq!(entry.is_dir(), true); assert_eq!(entry.is_file(), false); - assert_eq!(entry.get_unix_pex(), Some((7, 5, 5))); + assert_eq!( + entry.get_unix_pex(), + Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))) + ); assert_eq!(entry.unwrap_dir().abs_path, PathBuf::from("/foo")); } @@ -294,10 +358,10 @@ mod tests { creation_time: t_now, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert_eq!(entry.get_abs_path(), PathBuf::from("/bar.txt")); assert_eq!(entry.get_name(), String::from("bar.txt")); @@ -308,7 +372,10 @@ mod tests { assert_eq!(entry.get_ftype(), Some(String::from("txt"))); assert_eq!(entry.get_user(), Some(0)); assert_eq!(entry.get_group(), Some(0)); - assert_eq!(entry.get_unix_pex(), Some((6, 4, 4))); + assert_eq!( + entry.get_unix_pex(), + Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))) + ); assert_eq!(entry.is_symlink(), false); assert_eq!(entry.is_dir(), false); assert_eq!(entry.is_file(), true); @@ -327,10 +394,10 @@ mod tests { creation_time: t_now, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); entry.unwrap_dir(); } @@ -345,10 +412,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); entry.unwrap_file(); } @@ -364,10 +431,10 @@ mod tests { creation_time: t_now, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert_eq!(entry.is_hidden(), false); let entry: FsEntry = FsEntry::File(FsFile { @@ -378,10 +445,10 @@ mod tests { creation_time: t_now, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); assert_eq!(entry.is_hidden(), true); let entry: FsEntry = FsEntry::Directory(FsDirectory { @@ -390,10 +457,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); assert_eq!(entry.is_hidden(), true); } @@ -410,10 +477,10 @@ mod tests { creation_time: t_now, size: 8192, ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }); // Symlink is None... assert_eq!( @@ -427,10 +494,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(5), UnixPex::from(5))), // UNIX only }); assert_eq!(entry.get_realfile().get_abs_path(), PathBuf::from("/foo")); } @@ -446,10 +513,10 @@ mod tests { last_change_time: t_now, last_access_time: t_now, creation_time: t_now, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 7, 7)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(7), UnixPex::from(7), UnixPex::from(7))), // UNIX only }); let entry_child: FsEntry = FsEntry::Directory(FsDirectory { name: String::from("projects"), @@ -460,7 +527,7 @@ mod tests { symlink: Some(Box::new(entry_target)), user: Some(0), group: Some(0), - unix_pex: Some((7, 7, 7)), + unix_pex: Some((UnixPex::from(7), UnixPex::from(7), UnixPex::from(7))), }); let entry_root: FsEntry = FsEntry::File(FsFile { name: String::from("projects"), @@ -473,7 +540,7 @@ mod tests { symlink: Some(Box::new(entry_child)), user: Some(0), group: Some(0), - unix_pex: Some((7, 7, 7)), + unix_pex: Some((UnixPex::from(7), UnixPex::from(7), UnixPex::from(7))), }); assert_eq!(entry_root.is_symlink(), true); // get real file @@ -484,4 +551,28 @@ mod tests { PathBuf::from("/home/cvisintin/projects") ); } + + #[test] + fn unix_pex() { + let pex: UnixPex = UnixPex::from(4); + assert_eq!(pex.can_read(), true); + assert_eq!(pex.can_write(), false); + assert_eq!(pex.can_execute(), false); + let pex: UnixPex = UnixPex::from(0); + assert_eq!(pex.can_read(), false); + assert_eq!(pex.can_write(), false); + assert_eq!(pex.can_execute(), false); + let pex: UnixPex = UnixPex::from(3); + assert_eq!(pex.can_read(), false); + assert_eq!(pex.can_write(), true); + assert_eq!(pex.can_execute(), true); + let pex: UnixPex = UnixPex::from(7); + assert_eq!(pex.can_read(), true); + assert_eq!(pex.can_write(), true); + assert_eq!(pex.can_execute(), true); + let pex: UnixPex = UnixPex::from(3); + assert_eq!(pex.as_byte(), 3); + let pex: UnixPex = UnixPex::from(7); + assert_eq!(pex.as_byte(), 7); + } } diff --git a/src/host/mod.rs b/src/host/mod.rs index 9eb434a..593ab74 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -38,7 +38,7 @@ use std::fs::set_permissions; use std::os::unix::fs::{MetadataExt, PermissionsExt}; // Locals -use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; /// ## HostErrorType /// @@ -784,10 +784,10 @@ impl Localhost { /// /// Return string with format xxxxxx to tuple of permissions (user, group, others) #[cfg(target_family = "unix")] - fn u32_to_mode(&self, mode: u32) -> (u8, u8, u8) { - let user: u8 = ((mode >> 6) & 0x7) as u8; - let group: u8 = ((mode >> 3) & 0x7) as u8; - let others: u8 = (mode & 0x7) as u8; + fn u32_to_mode(&self, mode: u32) -> (UnixPex, UnixPex, UnixPex) { + let user: UnixPex = UnixPex::from(((mode >> 6) & 0x7) as u8); + let group: UnixPex = UnixPex::from(((mode >> 3) & 0x7) as u8); + let others: UnixPex = UnixPex::from((mode & 0x7) as u8); (user, group, others) } diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 77eee6b..a9a4eb7 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -707,13 +707,16 @@ impl FileTransferActivity { target_os = "macos", target_os = "linux" ))] - if let Some(pex) = dir.unix_pex { - if let Err(err) = self.host.chmod(local_dir_path.as_path(), pex) { + if let Some((owner, group, others)) = dir.unix_pex { + if let Err(err) = self.host.chmod( + local_dir_path.as_path(), + (owner.as_byte(), group.as_byte(), others.as_byte()), + ) { self.log( LogLevel::Error, format!( "Could not apply file mode {:?} to \"{}\": {}", - pex, + (owner.as_byte(), group.as_byte(), others.as_byte()), local_dir_path.display(), err ), @@ -874,13 +877,16 @@ impl FileTransferActivity { target_os = "macos", target_os = "linux" ))] - if let Some(pex) = remote.unix_pex { - if let Err(err) = self.host.chmod(local, pex) { + if let Some((owner, group, others)) = remote.unix_pex { + if let Err(err) = self + .host + .chmod(local, (owner.as_byte(), group.as_byte(), others.as_byte())) + { self.log( LogLevel::Error, format!( "Could not apply file mode {:?} to \"{}\": {}", - pex, + (owner.as_byte(), group.as_byte(), others.as_byte()), local.display(), err ), diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 44f8f6a..60f426b 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -25,6 +25,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +use crate::fs::UnixPex; + use chrono::prelude::*; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; @@ -32,55 +34,23 @@ use tuirealm::tui::style::Color; /// ### fmt_pex /// -/// Convert 3 bytes of permissions value into ls notation (e.g. rwx-wx--x) -pub fn fmt_pex(owner: u8, group: u8, others: u8) -> String { - let mut mode: String = String::with_capacity(9); - let read: u8 = (owner >> 2) & 0x1; - let write: u8 = (owner >> 1) & 0x1; - let exec: u8 = owner & 0x1; - mode.push_str(match read { - 1 => "r", - _ => "-", - }); - mode.push_str(match write { - 1 => "w", - _ => "-", - }); - mode.push_str(match exec { - 1 => "x", - _ => "-", - }); - let read: u8 = (group >> 2) & 0x1; - let write: u8 = (group >> 1) & 0x1; - let exec: u8 = group & 0x1; - mode.push_str(match read { - 1 => "r", - _ => "-", - }); - mode.push_str(match write { - 1 => "w", - _ => "-", - }); - mode.push_str(match exec { - 1 => "x", - _ => "-", - }); - let read: u8 = (others >> 2) & 0x1; - let write: u8 = (others >> 1) & 0x1; - let exec: u8 = others & 0x1; - mode.push_str(match read { - 1 => "r", - _ => "-", - }); - mode.push_str(match write { - 1 => "w", - _ => "-", - }); - mode.push_str(match exec { - 1 => "x", - _ => "-", - }); - mode +/// Convert permissions bytes of permissions value into ls notation (e.g. rwx,-wx,--x) +pub fn fmt_pex(pex: UnixPex) -> String { + format!( + "{}{}{}", + match pex.can_read() { + true => 'r', + false => '-', + }, + match pex.can_write() { + true => 'w', + false => '-', + }, + match pex.can_execute() { + true => 'x', + false => '-', + } + ) } /// ### instant_to_str @@ -326,14 +296,9 @@ mod tests { #[test] fn test_utils_fmt_pex() { - assert_eq!(fmt_pex(7, 7, 7), String::from("rwxrwxrwx")); - assert_eq!(fmt_pex(7, 5, 5), String::from("rwxr-xr-x")); - assert_eq!(fmt_pex(6, 6, 6), String::from("rw-rw-rw-")); - assert_eq!(fmt_pex(6, 4, 4), String::from("rw-r--r--")); - assert_eq!(fmt_pex(6, 0, 0), String::from("rw-------")); - assert_eq!(fmt_pex(0, 0, 0), String::from("---------")); - assert_eq!(fmt_pex(4, 4, 4), String::from("r--r--r--")); - assert_eq!(fmt_pex(1, 2, 1), String::from("--x-w---x")); + assert_eq!(fmt_pex(UnixPex::from(7)), String::from("rwx")); + assert_eq!(fmt_pex(UnixPex::from(5)), String::from("r-xr-x")); + assert_eq!(fmt_pex(UnixPex::from(6)), String::from("rw-")); } #[test] diff --git a/src/utils/test_helpers.rs b/src/utils/test_helpers.rs index 8706900..8ec02f0 100644 --- a/src/utils/test_helpers.rs +++ b/src/utils/test_helpers.rs @@ -25,7 +25,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -use crate::fs::{FsDirectory, FsEntry, FsFile}; +use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; // ext use std::fs::File; #[cfg(feature = "with-containers")] @@ -53,11 +53,11 @@ pub fn create_sample_file_entry() -> (FsFile, NamedTempFile) { last_access_time: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, size: 127, - ftype: None, // File type - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + ftype: None, // File type + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }, tmpfile, ) @@ -161,10 +161,10 @@ pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry { last_change_time: SystemTime::UNIX_EPOCH, last_access_time: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }), false => FsEntry::File(FsFile { name: path.file_name().unwrap().to_string_lossy().to_string(), @@ -173,11 +173,11 @@ pub fn make_fsentry(path: PathBuf, is_dir: bool) -> FsEntry { last_access_time: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, size: 127, - ftype: None, // File type - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only + ftype: None, // File type + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((UnixPex::from(6), UnixPex::from(4), UnixPex::from(4))), // UNIX only }), } } From 4bbb33984496afca21f588cbc89f05f04dbf6133 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 17:57:18 +0200 Subject: [PATCH 15/20] test fixed --- src/utils/fmt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 60f426b..90e0194 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -297,7 +297,7 @@ mod tests { #[test] fn test_utils_fmt_pex() { assert_eq!(fmt_pex(UnixPex::from(7)), String::from("rwx")); - assert_eq!(fmt_pex(UnixPex::from(5)), String::from("r-xr-x")); + assert_eq!(fmt_pex(UnixPex::from(5)), String::from("r-x")); assert_eq!(fmt_pex(UnixPex::from(6)), String::from("rw-")); } From 0cb9254e63f57bcf73c096992f17885bb61386cf Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 23 Aug 2021 18:01:45 +0200 Subject: [PATCH 16/20] fmt --- src/filetransfer/sftp_transfer.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 00c50e0..ca4ea96 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -720,7 +720,11 @@ impl FileTransfer for SftpFileTransfer { // Calculate file mode let mode: i32 = match local.unix_pex { None => 0o644, - Some((u, g, o)) => ((u.as_byte() as i32) << 6) + ((g.as_byte() as i32) << 3) + (o.as_byte() as i32), + Some((u, g, o)) => { + ((u.as_byte() as i32) << 6) + + ((g.as_byte() as i32) << 3) + + (o.as_byte() as i32) + } }; debug!("File mode {:?}", mode); match sftp.open_mode( From 214ec0c5a54e4f0481994f34dce88f189600c074 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 24 Aug 2021 09:28:49 +0200 Subject: [PATCH 17/20] absolutize path common functions --- src/filetransfer/ftp_transfer.rs | 10 ++---- src/filetransfer/scp_transfer.rs | 43 +++++++++++--------------- src/host/mod.rs | 11 ++----- src/ui/activities/filetransfer/misc.rs | 19 ++---------- src/utils/mod.rs | 1 + 5 files changed, 26 insertions(+), 58 deletions(-) diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 2530abd..b0f5b82 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -28,6 +28,7 @@ use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; use crate::utils::fmt::shadow_password; +use crate::utils::path; // Includes use std::convert::TryFrom; @@ -125,14 +126,7 @@ impl FtpFileTransfer { None => None, Some(p) => { // Make abs path - let abs_path: PathBuf = match p.is_absolute() { - true => p.to_path_buf(), - false => { - let mut abs = wrkdir.to_path_buf(); - abs.push(p); - abs - } - }; + let abs_path: PathBuf = path::absolutize(wrkdir, p); Some(Box::new(FsEntry::File(FsFile { name: p .file_name() diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 86df810..d3b00de 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -76,6 +76,21 @@ impl ScpFileTransfer { p.to_path_buf() } + /// ### absolutize + /// + /// Absolutize target path if relative. + /// This also converts backslashes to slashes if relative + fn absolutize(wrkdir: &Path, target: &Path) -> PathBuf { + match target.is_absolute() { + true => target.to_path_buf(), + false => { + let mut p: PathBuf = wrkdir.to_path_buf(); + p.push(target); + Self::resolve(p.as_path()) + } + } + } + /// ### parse_ls_output /// /// Parse a line of `ls -l` output and tokenize the output into a `FsEntry` @@ -506,14 +521,7 @@ impl FileTransfer for ScpFileTransfer { match self.is_connected() { true => { let p: PathBuf = self.wrkdir.clone(); - let remote_path: PathBuf = match dir.is_absolute() { - true => PathBuf::from(dir), - false => { - let mut p: PathBuf = PathBuf::from("."); - p.push(dir); - Self::resolve(p.as_path()) - } - }; + let remote_path: PathBuf = Self::absolutize(&Path::new("."), dir); info!("Changing working directory to {}", remote_path.display()); // Change directory match self.perform_shell_cmd_with_path( @@ -774,14 +782,7 @@ impl FileTransfer for ScpFileTransfer { /// /// Stat file and return FsEntry fn stat(&mut self, path: &Path) -> Result { - let path: PathBuf = match path.is_absolute() { - true => PathBuf::from(path), - false => { - let mut p: PathBuf = self.wrkdir.clone(); - p.push(path); - Self::resolve(p.as_path()) - } - }; + let path: PathBuf = Self::absolutize(self.wrkdir.as_path(), path); match self.is_connected() { true => { let p: PathBuf = self.wrkdir.clone(); @@ -857,15 +858,7 @@ impl FileTransfer for ScpFileTransfer { ) -> Result, FileTransferError> { match self.session.as_ref() { Some(session) => { - let file_name: PathBuf = match file_name.is_absolute() { - true => PathBuf::from(file_name), - false => { - let mut p: PathBuf = self.wrkdir.clone(); - p.push(file_name); - Self::resolve(p.as_path()) - } - }; - let file_name: PathBuf = Self::resolve(file_name.as_path()); + let file_name: PathBuf = Self::absolutize(self.wrkdir.as_path(), file_name); info!( "Sending file {} to {}", local.abs_path.display(), diff --git a/src/host/mod.rs b/src/host/mod.rs index 593ab74..4440840 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -39,6 +39,7 @@ use std::os::unix::fs::{MetadataExt, PermissionsExt}; // Locals use crate::fs::{FsDirectory, FsEntry, FsFile, UnixPex}; +use crate::utils::path; /// ## HostErrorType /// @@ -803,15 +804,7 @@ impl Localhost { /// /// Convert path to absolute path fn to_abs_path(&self, p: &Path) -> PathBuf { - // Convert to abs path - match p.is_relative() { - true => { - let mut path: PathBuf = self.wrkdir.clone(); - path.push(p); - path - } - false => PathBuf::from(p), - } + path::absolutize(self.wrkdir.as_path(), p) } } diff --git a/src/ui/activities/filetransfer/misc.rs b/src/ui/activities/filetransfer/misc.rs index 0d1c4ac..60360d7 100644 --- a/src/ui/activities/filetransfer/misc.rs +++ b/src/ui/activities/filetransfer/misc.rs @@ -25,6 +25,7 @@ use super::{ConfigClient, FileTransferActivity, LogLevel, LogRecord}; use crate::system::environment; use crate::system::sshkey_storage::SshKeyStorage; +use crate::utils::path; // Ext use std::env; use std::path::{Path, PathBuf}; @@ -124,27 +125,13 @@ impl FileTransferActivity { /// /// Convert a path to absolute according to local explorer pub(super) fn local_to_abs_path(&self, path: &Path) -> PathBuf { - match path.is_relative() { - true => { - let mut d: PathBuf = self.local().wrkdir.clone(); - d.push(path); - d - } - false => path.to_path_buf(), - } + path::absolutize(self.local().wrkdir.as_path(), path) } /// ### remote_to_abs_path /// /// Convert a path to absolute according to remote explorer pub(super) fn remote_to_abs_path(&self, path: &Path) -> PathBuf { - match path.is_relative() { - true => { - let mut wrkdir: PathBuf = self.remote().wrkdir.clone(); - wrkdir.push(path); - wrkdir - } - false => path.to_path_buf(), - } + path::absolutize(self.remote().wrkdir.as_path(), path) } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f956857..71d2835 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -31,6 +31,7 @@ pub mod file; pub mod fmt; pub mod git; pub mod parser; +pub mod path; pub mod random; pub mod ui; From 91ce709d7bd5208ec3b1bdadad0a1b650f7c4433 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 24 Aug 2021 09:29:23 +0200 Subject: [PATCH 18/20] lint --- src/filetransfer/scp_transfer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index d3b00de..400b035 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -521,7 +521,7 @@ impl FileTransfer for ScpFileTransfer { match self.is_connected() { true => { let p: PathBuf = self.wrkdir.clone(); - let remote_path: PathBuf = Self::absolutize(&Path::new("."), dir); + let remote_path: PathBuf = Self::absolutize(Path::new("."), dir); info!("Changing working directory to {}", remote_path.display()); // Change directory match self.perform_shell_cmd_with_path( From 78b918087e0255db5867214701148d360b3c0b4c Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 24 Aug 2021 09:29:40 +0200 Subject: [PATCH 19/20] absolutize path common functions --- src/utils/path.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/utils/path.rs diff --git a/src/utils/path.rs b/src/utils/path.rs new file mode 100644 index 0000000..1621009 --- /dev/null +++ b/src/utils/path.rs @@ -0,0 +1,43 @@ +//! # Path +//! +//! Path related utilities + +use std::path::{Path, PathBuf}; + +/// ### absolutize +/// +/// Absolutize target path if relative. +/// For example: +/// +/// ```rust +/// assert_eq!(absolutize(&Path::new("/home/omar"), &Path::new("readme.txt")).as_path(), Path::new("/home/omar/readme.txt")); +/// assert_eq!(absolutize(&Path::new("/home/omar"), &Path::new("/tmp/readme.txt")).as_path(), Path::new("/tmp/readme.txt")); +/// ``` +pub fn absolutize(wrkdir: &Path, target: &Path) -> PathBuf { + match target.is_absolute() { + true => target.to_path_buf(), + false => { + let mut p: PathBuf = wrkdir.to_path_buf(); + p.push(target); + p + } + } +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn absolutize_path() { + assert_eq!( + absolutize(&Path::new("/home/omar"), &Path::new("readme.txt")).as_path(), + Path::new("/home/omar/readme.txt") + ); + assert_eq!( + absolutize(&Path::new("/home/omar"), &Path::new("/tmp/readme.txt")).as_path(), + Path::new("/tmp/readme.txt") + ); + } +} From 22bb143820f3c87aff11ab307869155f9976d2d5 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 24 Aug 2021 15:03:20 +0200 Subject: [PATCH 20/20] 0.6.1 --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b50fa4..05f6c4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ ## 0.6.1 -Released on ?? +Released on 31/08/2021 - Enhancements: - Now that tui-rs supports title alignment, UI has been improved diff --git a/README.md b/README.md index 49853a2..bf29840 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

Developed by @veeso

-

Current version: 0.6.1 (FIXME: 23/07/2021)

+

Current version: 0.6.1 (31/08/2021)

[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.6.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp)