From ec4daf8e25df1c4b1abaf58093485996e8058f3f Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 4 Jan 2022 12:48:36 +0100 Subject: [PATCH] remotefs 0.2.0 --- CHANGELOG.md | 2 +- Cargo.lock | 57 +- Cargo.toml | 11 +- docs/developer.md | 2 +- src/explorer/formatter.rs | 222 ++++---- src/explorer/mod.rs | 149 +++-- src/filetransfer/builder.rs | 8 +- src/host/mod.rs | 395 +++++--------- src/system/sshkey_storage.rs | 2 +- .../filetransfer/actions/change_dir.rs | 14 +- .../activities/filetransfer/actions/copy.rs | 204 ++++--- .../activities/filetransfer/actions/delete.rs | 20 +- .../activities/filetransfer/actions/edit.rs | 43 +- .../activities/filetransfer/actions/find.rs | 53 +- src/ui/activities/filetransfer/actions/mod.rs | 80 +-- .../filetransfer/actions/newfile.rs | 10 +- .../activities/filetransfer/actions/open.rs | 53 +- .../activities/filetransfer/actions/rename.rs | 20 +- .../activities/filetransfer/actions/save.rs | 44 +- .../activities/filetransfer/actions/submit.rs | 110 ++-- .../filetransfer/actions/symlink.rs | 6 +- .../filetransfer/components/popups.rs | 22 +- src/ui/activities/filetransfer/lib/browser.rs | 4 +- src/ui/activities/filetransfer/misc.rs | 3 +- src/ui/activities/filetransfer/session.rs | 516 +++++++++--------- src/ui/activities/filetransfer/update.rs | 16 +- src/ui/activities/filetransfer/view.rs | 6 +- src/utils/test_helpers.rs | 36 +- 28 files changed, 1013 insertions(+), 1095 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6b5758..955f7d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ Released on FIXME: - Dependencies: - Updated `tui-realm` to `1.3.0` - Updated `tui-realm-stdlib` to `1.1.4` - - Removed `rust-s3`, `ssh2`, `suppaftp`; replaced by `remotefs 0.1.1` + - Removed `rust-s3`, `ssh2`, `suppaftp`; replaced by `remotefs 0.2.0`, `remotefs-aws-s3 0.1.0`, `remotefs-ftp 0.1.0` and `remotefs-ssh 0.1.0` - Removed `crossterm` (since bridged by tui-realm) ## 0.7.0 diff --git a/Cargo.lock b/Cargo.lock index 6d1e714..1aa0900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1676,22 +1676,59 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remotefs" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c72915b01014a11d7e21b3a28141ff32b881bd8103c0f65269cc7932a03ae61c" +checksum = "e8df5d94ca7480505315216544f145f358b7609781beef33a9149ecd9be44a5b" +dependencies = [ + "chrono", + "log", + "thiserror", + "wildmatch", +] + +[[package]] +name = "remotefs-aws-s3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b130b79d6ade3821554c46a87fd54dea024255daa5ecac5e9f68e2a4d3230a" +dependencies = [ + "chrono", + "log", + "path-slash", + "remotefs", + "rust-s3", + "thiserror", + "users", +] + +[[package]] +name = "remotefs-ftp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "359fd989a6ad50fa6defae771e453695412cbfdb5476f49e42d1bb1d51b5c096" +dependencies = [ + "log", + "path-slash", + "remotefs", + "suppaftp", + "users", +] + +[[package]] +name = "remotefs-ssh" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c6959740eeee7e773166e79dd52c7dea41f791456aa5addfb949c61c15b27" dependencies = [ "chrono", "lazy_static", "log", "path-slash", "regex", - "rust-s3", + "remotefs", "ssh2", "ssh2-config", - "suppaftp", - "thiserror", "users", - "wildmatch", ] [[package]] @@ -2095,10 +2132,11 @@ dependencies = [ [[package]] name = "ssh2-config" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e64d0ea4897c9415c34011a4cdf21a0e0168c200595f1f543be1ca807942d8" +checksum = "0e55cdf8a42b24c57788bc8b85e8d6e7f6e0614b5316322b2e7f455f9d93230e" dependencies = [ + "dirs 4.0.0", "thiserror", "wildmatch", ] @@ -2236,6 +2274,9 @@ dependencies = [ "rand 0.8.4", "regex", "remotefs", + "remotefs-aws-s3", + "remotefs-ftp", + "remotefs-ssh", "rpassword", "self_update", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3a2cf96..9acac0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,10 @@ notify-rust = { version = "4.5.3", default-features = false, features = [ "d" ] open = "2.0.1" rand = "0.8.4" regex = "1.5.4" -remotefs = { version = "0.1.1", features = [ "aws-s3", "ftp", "ssh" ] } +remotefs = "^0.2.0" +remotefs-aws-s3 = "^0.1.0" +remotefs-ftp = { version = "^0.1.0", features = [ "secure" ] } +remotefs-ssh = "^0.1.0" rpassword = "5.0.1" self_update = { version = "0.27.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] } serde = { version = "^1.0.0", features = [ "derive" ] } @@ -74,3 +77,9 @@ with-keyring = [ "keyring" ] [target."cfg(target_family = \"unix\")"] [target."cfg(target_family = \"unix\")".dependencies] users = "0.11.0" + +[profile.release] +incremental = true + +[profile.dev] +incremental = true diff --git a/docs/developer.md b/docs/developer.md index a9d606f..11023b6 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -24,7 +24,7 @@ termscp is basically made up of 4 components: In addition to the 4 main components, other have been added through the time: - **config**: this module provides the configuration schema and serialization methods for it. -- **fs**: this modules exposes the FsEntry entity and the explorers. The explorers are structs which hold the content of the current directory; they also they take of filtering files up to your preferences and format file entries based on your configuration. +- **fs**: this modules exposes the FsFile entity and the explorers. The explorers are structs which hold the content of the current directory; they also they take of filtering files up to your preferences and format file entries based on your configuration. - **system**: the system module provides a way to actually interact with the configuration, the ssh key storage and with the bookmarks. - **utils**: contains the utilities used by pretty much all the project. diff --git a/src/explorer/formatter.rs b/src/explorer/formatter.rs index e2f61d3..24f136b 100644 --- a/src/explorer/formatter.rs +++ b/src/explorer/formatter.rs @@ -31,13 +31,14 @@ use crate::utils::path::diff_paths; // Ext use bytesize::ByteSize; use regex::Regex; -use remotefs::Entry; +use remotefs::File; use std::path::PathBuf; +use std::time::UNIX_EPOCH; #[cfg(target_family = "unix")] use users::{get_group_by_gid, get_user_by_uid}; // Types -// FmtCallback: Formatter, fsentry: &Entry, cur_str, prefix, length, extra -type FmtCallback = fn(&Formatter, &Entry, &str, &str, Option<&usize>, Option<&String>) -> String; +// FmtCallback: Formatter, fsentry: &File, cur_str, prefix, length, extra +type FmtCallback = fn(&Formatter, &File, &str, &str, Option<&usize>, Option<&String>) -> String; // Keys const FMT_KEY_ATIME: &str = "ATIME"; @@ -64,7 +65,7 @@ lazy_static! { static ref FMT_ATTR_REGEX: Regex = Regex::new(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?").ok().unwrap(); } -/// Call Chain block is a block in a chain of functions which are called in order to format the Entry. +/// Call Chain block is a block in a chain of functions which are called in order to format the File. /// A callChain is instantiated starting from the Formatter syntax and the regex, once the groups are found /// a chain of function is made using the Formatters method. /// This method provides an extremely fast way to format fs entries @@ -99,7 +100,7 @@ impl CallChainBlock { } /// Call next callback in the CallChain - pub fn next(&self, fmt: &Formatter, fsentry: &Entry, cur_str: &str) -> String { + pub fn next(&self, fmt: &Formatter, fsentry: &File, cur_str: &str) -> String { // Call func let new_str: String = (self.func)( fmt, @@ -161,7 +162,7 @@ impl Formatter { } /// Format fsentry - pub fn fmt(&self, fsentry: &Entry) -> String { + pub fn fmt(&self, fsentry: &File) -> String { // Execute callchain blocks self.call_chain.next(self, fsentry, "") } @@ -171,7 +172,7 @@ impl Formatter { /// Format last access time fn fmt_atime( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -179,7 +180,7 @@ impl Formatter { ) -> String { // Get date (use extra args as format or default "%b %d %Y %H:%M") let datetime: String = fmt_time( - fsentry.metadata().atime, + fsentry.metadata().accessed.unwrap_or(UNIX_EPOCH), match fmt_extra { Some(fmt) => fmt.as_ref(), None => "%b %d %Y %H:%M", @@ -198,7 +199,7 @@ impl Formatter { /// Format creation time fn fmt_ctime( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -206,7 +207,7 @@ impl Formatter { ) -> String { // Get date let datetime: String = fmt_time( - fsentry.metadata().ctime, + fsentry.metadata().created.unwrap_or(UNIX_EPOCH), match fmt_extra { Some(fmt) => fmt.as_ref(), None => "%b %d %Y %H:%M", @@ -225,7 +226,7 @@ impl Formatter { /// Format owner group fn fmt_group( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -258,7 +259,7 @@ impl Formatter { /// Format last change time fn fmt_mtime( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -266,7 +267,7 @@ impl Formatter { ) -> String { // Get date let datetime: String = fmt_time( - fsentry.metadata().mtime, + fsentry.metadata().modified.unwrap_or(UNIX_EPOCH), match fmt_extra { Some(fmt) => fmt.as_ref(), None => "%b %d %Y %H:%M", @@ -285,7 +286,7 @@ impl Formatter { /// Format file name fn fmt_name( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -296,14 +297,14 @@ impl Formatter { Some(l) => *l, None => 24, }; - let name: &str = fsentry.name(); + let name = fsentry.name(); let last_idx: usize = match fsentry.is_dir() { // NOTE: For directories is l - 2, since we push '/' to name true => file_len - 2, false => file_len - 1, }; let mut name: String = match name.len() >= file_len { - false => name.to_string(), + false => name, true => format!("{}…", &name[0..last_idx]), }; if fsentry.is_dir() { @@ -316,7 +317,7 @@ impl Formatter { /// Format path fn fmt_path( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -341,7 +342,7 @@ impl Formatter { /// Format file permissions fn fmt_pex( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, _fmt_len: Option<&usize>, @@ -376,7 +377,7 @@ impl Formatter { /// Format file size fn fmt_size( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, _fmt_len: Option<&usize>, @@ -387,6 +388,17 @@ impl Formatter { let size: ByteSize = ByteSize(fsentry.metadata().size); // Add to cur str, prefix and the key value format!("{}{}{:10}", cur_str, prefix, size.to_string()) + } else if fsentry.metadata().symlink.is_some() { + let size = ByteSize( + fsentry + .metadata() + .symlink + .as_ref() + .unwrap() + .to_string_lossy() + .len() as u64, + ); + format!("{}{}{:10}", cur_str, prefix, size.to_string()) } else { // Add to cur str, prefix and the key value format!("{}{} ", cur_str, prefix) @@ -396,7 +408,7 @@ impl Formatter { /// Format file symlink (if any) fn fmt_symlink( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, fmt_len: Option<&usize>, @@ -423,7 +435,7 @@ impl Formatter { /// Format owner user fn fmt_user( &self, - fsentry: &Entry, + fsentry: &File, cur_str: &str, prefix: &str, _fmt_len: Option<&usize>, @@ -451,7 +463,7 @@ impl Formatter { /// It does nothing, just returns cur_str fn fmt_fallback( &self, - _fsentry: &Entry, + _fsentry: &File, cur_str: &str, prefix: &str, _fmt_len: Option<&usize>, @@ -536,7 +548,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; - use remotefs::fs::{Directory, File, Metadata, UnixPex}; + use remotefs::fs::{File, FileType, Metadata, UnixPex}; use std::path::PathBuf; use std::time::SystemTime; @@ -546,21 +558,20 @@ mod tests { let dummy_formatter: Formatter = Formatter::new(""); // Make a dummy entry let t: SystemTime = SystemTime::now(); - let dummy_entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let dummy_entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, symlink: None, uid: Some(0), gid: Some(0), mode: Some(UnixPex::from(0o644)), }, - }); + }; let prefix: String = String::from("h"); let mut callchain: CallChainBlock = CallChainBlock::new(dummy_fmt, prefix, None, None); assert!(callchain.next_block.is_none()); @@ -588,21 +599,20 @@ mod tests { let formatter: Formatter = Formatter::default(); // Experiments :D let t: SystemTime = SystemTime::now(); - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, symlink: None, uid: Some(0), gid: Some(0), mode: Some(UnixPex::from(0o644)), }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), @@ -620,21 +630,20 @@ mod tests { ) ); // Elide name - let entry: Entry = Entry::File(File { - name: String::from("piroparoporoperoperupupu.txt"), - path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), + let entry = File { + path: PathBuf::from("/piroparoporoperoperupupu.txt"), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, symlink: None, uid: Some(0), gid: Some(0), mode: Some(UnixPex::from(0o644)), }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), @@ -652,21 +661,20 @@ mod tests { ) ); // No pex - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, symlink: None, uid: Some(0), gid: Some(0), mode: None, }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), @@ -684,21 +692,20 @@ mod tests { ) ); // No user - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, symlink: None, uid: None, gid: Some(0), mode: None, }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), @@ -723,20 +730,20 @@ mod tests { let formatter: Formatter = Formatter::default(); // Experiments :D let t: SystemTime = SystemTime::now(); - let entry: Entry = Entry::Directory(Directory { - name: String::from("projects"), + let entry = File { path: PathBuf::from("/home/cvisintin/projects"), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::Directory, size: 4096, symlink: None, uid: Some(0), gid: Some(0), mode: Some(UnixPex::from(0o755)), }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), @@ -754,20 +761,20 @@ mod tests { ) ); // No pex, no user - let entry: Entry = Entry::Directory(Directory { - name: String::from("projects"), + let entry = File { path: PathBuf::from("/home/cvisintin/projects"), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::Directory, size: 4096, symlink: None, uid: None, gid: Some(0), mode: None, }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( formatter.fmt(&entry), @@ -792,41 +799,41 @@ mod tests { Formatter::new("{NAME:16} {SYMLINK:12} {GROUP} {USER} {PEX} {SIZE} {ATIME:20:%a %b %d %Y %H:%M} {CTIME:20:%a %b %d %Y %H:%M} {MTIME:20:%a %b %d %Y %H:%M}"); // Directory (with symlink) let t: SystemTime = SystemTime::now(); - let entry: Entry = Entry::Directory(Directory { - name: String::from("projects"), - path: PathBuf::from("/home/cvisintin/project"), + let entry = File { + path: PathBuf::from("/home/cvisintin/projects"), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::Symlink, size: 4096, symlink: Some(PathBuf::from("project.info")), uid: None, gid: None, mode: Some(UnixPex::from(0o755)), }, - }); + }; assert_eq!(formatter.fmt(&entry), format!( - "projects/ -> project.info 0 0 lrwxr-xr-x {} {} {}", + "projects -> project.info 0 0 lrwxr-xr-x 12 B {} {} {}", fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"), )); // Directory without symlink - let entry: Entry = Entry::Directory(Directory { - name: String::from("projects"), - path: PathBuf::from("/home/cvisintin/project"), + let entry = File { + path: PathBuf::from("/home/cvisintin/projects"), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::Directory, size: 4096, symlink: None, uid: None, gid: None, mode: Some(UnixPex::from(0o755)), }, - }); + }; assert_eq!(formatter.fmt(&entry), format!( "projects/ 0 0 drwxr-xr-x {} {} {}", fmt_time(t, "%a %b %d %Y %H:%M"), @@ -834,43 +841,41 @@ mod tests { fmt_time(t, "%a %b %d %Y %H:%M"), )); // File with symlink - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::Symlink, size: 8192, symlink: Some(PathBuf::from("project.info")), uid: None, gid: None, mode: Some(UnixPex::from(0o644)), }, - }); + }; assert_eq!(formatter.fmt(&entry), format!( - "bar.txt -> project.info 0 0 lrw-r--r-- 8.2 KB {} {} {}", + "bar.txt -> project.info 0 0 lrw-r--r-- 12 B {} {} {}", fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"), )); // File without symlink - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, symlink: None, uid: None, gid: None, mode: Some(UnixPex::from(0o644)), }, - }); + }; assert_eq!(formatter.fmt(&entry), format!( "bar.txt 0 0 -rw-r--r-- 8.2 KB {} {} {}", fmt_time(t, "%a %b %d %Y %H:%M"), @@ -883,21 +888,20 @@ mod tests { #[cfg(target_family = "unix")] fn should_fmt_path() { let t: SystemTime = SystemTime::now(); - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/tmp/a/b/c/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::Symlink, size: 8192, symlink: Some(PathBuf::from("project.info")), uid: None, gid: None, mode: Some(UnixPex::from(0o644)), }, - }); + }; let formatter: Formatter = Formatter::new("File path: {PATH}"); assert_eq!( formatter.fmt(&entry).as_str(), @@ -915,7 +919,7 @@ mod tests { /// Dummy formatter, just yelds an 'A' at the end of the current string fn dummy_fmt( _fmt: &Formatter, - _entry: &Entry, + _entry: &File, cur_str: &str, prefix: &str, _fmt_len: Option<&usize>, diff --git a/src/explorer/mod.rs b/src/explorer/mod.rs index ff183fd..0639af2 100644 --- a/src/explorer/mod.rs +++ b/src/explorer/mod.rs @@ -31,7 +31,7 @@ mod formatter; // Locals use formatter::Formatter; // Ext -use remotefs::fs::Entry; +use remotefs::fs::File; use std::cmp::Reverse; use std::collections::VecDeque; use std::path::{Path, PathBuf}; @@ -71,8 +71,8 @@ pub struct FileExplorer { pub(crate) file_sorting: FileSorting, // File sorting criteria pub(crate) group_dirs: Option, // If Some, defines how to group directories pub(crate) opts: ExplorerOpts, // Explorer options - pub(crate) fmt: Formatter, // Entry formatter - files: Vec, // Files in directory + pub(crate) fmt: Formatter, // File formatter + files: Vec, // Files in directory } impl Default for FileExplorer { @@ -109,7 +109,7 @@ impl FileExplorer { /// Set Explorer files /// This method will also sort entries based on current options /// Once all sorting have been performed, index is moved to first valid entry. - pub fn set_files(&mut self, files: Vec) { + pub fn set_files(&mut self, files: Vec) { self.files = files; // Sort self.sort(); @@ -131,7 +131,7 @@ impl FileExplorer { /// Iterate over files /// Filters are applied based on current options (e.g. hidden files not returned) - pub fn iter_files(&self) -> impl Iterator + '_ { + pub fn iter_files(&self) -> impl Iterator + '_ { // Filter let opts: ExplorerOpts = self.opts; Box::new(self.files.iter().filter(move |x| { @@ -146,12 +146,12 @@ impl FileExplorer { } /// Iterate all files; doesn't care about options - pub fn iter_files_all(&self) -> impl Iterator + '_ { + pub fn iter_files_all(&self) -> impl Iterator + '_ { Box::new(self.files.iter()) } /// Get file at relative index - pub fn get(&self, idx: usize) -> Option<&Entry> { + pub fn get(&self, idx: usize) -> Option<&File> { let opts: ExplorerOpts = self.opts; let filtered = self .files @@ -172,7 +172,7 @@ impl FileExplorer { // Formatting /// Format a file entry - pub fn fmt_file(&self, entry: &Entry) -> String { + pub fn fmt_file(&self, entry: &File) -> String { self.fmt.fmt(entry) } @@ -222,35 +222,35 @@ impl FileExplorer { /// Sort explorer files by their name. All names are converted to lowercase fn sort_files_by_name(&mut self) { - self.files.sort_by_key(|x: &Entry| x.name().to_lowercase()); + self.files.sort_by_key(|x: &File| x.name().to_lowercase()); } /// Sort files by mtime; the newest comes first fn sort_files_by_mtime(&mut self) { self.files - .sort_by(|a: &Entry, b: &Entry| b.metadata().mtime.cmp(&a.metadata().mtime)); + .sort_by_key(|b: &File| Reverse(b.metadata().modified)); } /// Sort files by creation time; the newest comes first fn sort_files_by_creation_time(&mut self) { self.files - .sort_by_key(|b: &Entry| Reverse(b.metadata().ctime)); + .sort_by_key(|b: &File| Reverse(b.metadata().created)); } /// Sort files by size fn sort_files_by_size(&mut self) { self.files - .sort_by_key(|b: &Entry| Reverse(b.metadata().size)); + .sort_by_key(|b: &File| Reverse(b.metadata().size)); } /// Sort files; directories come first fn sort_files_directories_first(&mut self) { - self.files.sort_by_key(|x: &Entry| x.is_file()); + self.files.sort_by_key(|x: &File| !x.is_dir()); } /// Sort files; directories come last fn sort_files_directories_last(&mut self) { - self.files.sort_by_key(|x: &Entry| x.is_dir()); + self.files.sort_by_key(|x: &File| x.is_dir()); } /// Enable/disable hidden files @@ -317,7 +317,7 @@ mod tests { use crate::utils::fmt::fmt_time; use pretty_assertions::assert_eq; - use remotefs::fs::{Directory, File, Metadata, UnixPex}; + use remotefs::fs::{File, FileType, Metadata, UnixPex}; use std::thread::sleep; use std::time::{Duration, SystemTime}; @@ -371,8 +371,8 @@ mod tests { // Create files explorer.set_files(vec![ make_fs_entry("README.md", false), - make_fs_entry("src/", true), - make_fs_entry(".git/", true), + make_fs_entry("src", true), + make_fs_entry(".git", true), make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("codecov.yml", false), make_fs_entry(".gitignore", false), @@ -381,7 +381,7 @@ mod tests { assert!(explorer.get(100).is_none()); //assert_eq!(explorer.count(), 6); // Verify (files are sorted by name) - assert_eq!(explorer.files.get(0).unwrap().name(), ".git/"); + assert_eq!(explorer.files.get(0).unwrap().name(), ".git"); // Iter files (all) assert_eq!(explorer.iter_files_all().count(), 6); // Iter files (hidden excluded) (.git, .gitignore are hidden) @@ -398,7 +398,7 @@ mod tests { // Create files (files are then sorted by name) explorer.set_files(vec![ make_fs_entry("README.md", false), - make_fs_entry("src/", true), + make_fs_entry("src", true), make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CODE_OF_CONDUCT.md", false), make_fs_entry("CHANGELOG.md", false), @@ -410,39 +410,39 @@ mod tests { explorer.sort_by(FileSorting::Name); // First entry should be "Cargo.lock" assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); - // Last should be "src/" - assert_eq!(explorer.files.get(8).unwrap().name(), "src/"); + // Last should be "src" + assert_eq!(explorer.files.get(8).unwrap().name(), "src"); } #[test] fn test_fs_explorer_sort_by_mtime() { let mut explorer: FileExplorer = FileExplorer::default(); - let entry1: Entry = make_fs_entry("README.md", false); + let entry1: File = make_fs_entry("README.md", false); // Wait 1 sec sleep(Duration::from_secs(1)); - let entry2: Entry = make_fs_entry("CODE_OF_CONDUCT.md", false); + let entry2: File = 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::ModifyTime); // First entry should be "CODE_OF_CONDUCT.md" assert_eq!(explorer.files.get(0).unwrap().name(), "CODE_OF_CONDUCT.md"); - // Last should be "src/" + // Last should be "src" assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); } #[test] fn test_fs_explorer_sort_by_creation_time() { let mut explorer: FileExplorer = FileExplorer::default(); - let entry1: Entry = make_fs_entry("README.md", false); + let entry1: File = make_fs_entry("README.md", false); // Wait 1 sec sleep(Duration::from_secs(1)); - let entry2: Entry = make_fs_entry("CODE_OF_CONDUCT.md", false); + let entry2: File = 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::CreationTime); // First entry should be "CODE_OF_CONDUCT.md" assert_eq!(explorer.files.get(0).unwrap().name(), "CODE_OF_CONDUCT.md"); - // Last should be "src/" + // Last should be "src" assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); } @@ -452,12 +452,12 @@ mod tests { // Create files (files are then sorted by name) explorer.set_files(vec![ make_fs_entry_with_size("README.md", false, 1024), - make_fs_entry_with_size("src/", true, 4096), + make_fs_entry_with_size("src", true, 4096), make_fs_entry_with_size("CONTRIBUTING.md", false, 256), ]); explorer.sort_by(FileSorting::Size); // Directory has size 4096 - assert_eq!(explorer.files.get(0).unwrap().name(), "src/"); + assert_eq!(explorer.files.get(0).unwrap().name(), "src"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); assert_eq!(explorer.files.get(2).unwrap().name(), "CONTRIBUTING.md"); } @@ -468,8 +468,8 @@ mod tests { // Create files (files are then sorted by name) explorer.set_files(vec![ make_fs_entry("README.md", false), - make_fs_entry("src/", true), - make_fs_entry("docs/", true), + make_fs_entry("src", true), + make_fs_entry("docs", true), make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CODE_OF_CONDUCT.md", false), make_fs_entry("CHANGELOG.md", false), @@ -481,8 +481,8 @@ mod tests { explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::First)); // First entry should be "docs" - assert_eq!(explorer.files.get(0).unwrap().name(), "docs/"); - assert_eq!(explorer.files.get(1).unwrap().name(), "src/"); + assert_eq!(explorer.files.get(0).unwrap().name(), "docs"); + assert_eq!(explorer.files.get(1).unwrap().name(), "src"); // 3rd is file first for alphabetical order assert_eq!(explorer.files.get(2).unwrap().name(), "Cargo.lock"); // Last should be "README.md" (last file for alphabetical ordening) @@ -495,8 +495,8 @@ mod tests { // Create files (files are then sorted by name) explorer.set_files(vec![ make_fs_entry("README.md", false), - make_fs_entry("src/", true), - make_fs_entry("docs/", true), + make_fs_entry("src", true), + make_fs_entry("docs", true), make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CODE_OF_CONDUCT.md", false), make_fs_entry("CHANGELOG.md", false), @@ -508,8 +508,8 @@ mod tests { explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::Last)); // Last entry should be "src" - assert_eq!(explorer.files.get(8).unwrap().name(), "docs/"); - assert_eq!(explorer.files.get(9).unwrap().name(), "src/"); + assert_eq!(explorer.files.get(8).unwrap().name(), "docs"); + assert_eq!(explorer.files.get(9).unwrap().name(), "src"); // first is file for alphabetical order assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); // Last in files should be "README.md" (last file for alphabetical ordening) @@ -521,21 +521,20 @@ mod tests { let explorer: FileExplorer = FileExplorer::default(); // Create fs entry let t: SystemTime = SystemTime::now(); - let entry: Entry = Entry::File(File { - name: String::from("bar.txt"), + let entry = File { path: PathBuf::from("/bar.txt"), - extension: Some(String::from("txt")), metadata: Metadata { - atime: t, - ctime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: FileType::File, size: 8192, - mtime: t, symlink: None, uid: Some(0), gid: Some(0), mode: Some(UnixPex::from(0o644)), }, - }); + }; #[cfg(target_family = "unix")] assert_eq!( explorer.fmt_file(&entry), @@ -592,68 +591,60 @@ mod tests { // Create files (files are then sorted by name) explorer.set_files(vec![ make_fs_entry("CONTRIBUTING.md", false), - make_fs_entry("docs/", true), - make_fs_entry("src/", true), + make_fs_entry("docs", true), + make_fs_entry("src", true), make_fs_entry("README.md", false), ]); explorer.del_entry(0); assert_eq!(explorer.files.len(), 3); - assert_eq!(explorer.files[0].name(), "docs/"); + assert_eq!(explorer.files[0].name(), "docs"); explorer.del_entry(5); assert_eq!(explorer.files.len(), 3); } - fn make_fs_entry(name: &str, is_dir: bool) -> Entry { + fn make_fs_entry(name: &str, is_dir: bool) -> File { let t: SystemTime = SystemTime::now(); let metadata = Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: if is_dir { + FileType::Directory + } else { + FileType::File + }, symlink: None, gid: Some(0), uid: Some(0), mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), size: 64, }; - match is_dir { - false => Entry::File(File { - name: name.to_string(), - path: PathBuf::from(name), - extension: None, - metadata, - }), - true => Entry::Directory(Directory { - name: name.to_string(), - path: PathBuf::from(name), - metadata, - }), + File { + path: PathBuf::from(name), + metadata, } } - fn make_fs_entry_with_size(name: &str, is_dir: bool, size: usize) -> Entry { + fn make_fs_entry_with_size(name: &str, is_dir: bool, size: usize) -> File { let t: SystemTime = SystemTime::now(); let metadata = Metadata { - atime: t, - ctime: t, - mtime: t, + accessed: Some(t), + created: Some(t), + modified: Some(t), + file_type: if is_dir { + FileType::Directory + } else { + FileType::File + }, symlink: None, gid: Some(0), uid: Some(0), mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), size: size as u64, }; - match is_dir { - false => Entry::File(File { - name: name.to_string(), - path: PathBuf::from(name), - extension: None, - metadata, - }), - true => Entry::Directory(Directory { - name: name.to_string(), - path: PathBuf::from(name), - metadata, - }), + File { + path: PathBuf::from(name), + metadata, } } } diff --git a/src/filetransfer/builder.rs b/src/filetransfer/builder.rs index d389bde..cbd8cda 100644 --- a/src/filetransfer/builder.rs +++ b/src/filetransfer/builder.rs @@ -30,12 +30,10 @@ use super::{FileTransferProtocol, ProtocolParams}; use crate::system::config_client::ConfigClient; use crate::system::sshkey_storage::SshKeyStorage; -use remotefs::client::{ - aws_s3::AwsS3Fs, - ftp::FtpFs, - ssh::{ScpFs, SftpFs, SshOpts}, -}; use remotefs::RemoteFs; +use remotefs_aws_s3::AwsS3Fs; +use remotefs_ftp::FtpFs; +use remotefs_ssh::{ScpFs, SftpFs, SshOpts}; use std::path::PathBuf; /// Remotefs builder diff --git a/src/host/mod.rs b/src/host/mod.rs index a6c0796..e261db9 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -28,17 +28,16 @@ // ext #[cfg(target_family = "unix")] use remotefs::fs::UnixPex; -use remotefs::fs::{Directory, Entry, File, Metadata}; +use remotefs::fs::{File, FileType, Metadata}; use std::fs::{self, File as StdFile, OpenOptions}; use std::path::{Path, PathBuf}; -use std::time::SystemTime; use thiserror::Error; use wildmatch::WildMatch; // Metadata ext #[cfg(target_family = "unix")] use std::fs::set_permissions; #[cfg(target_family = "unix")] -use std::os::unix::fs::{MetadataExt, PermissionsExt}; +use std::os::unix::fs::PermissionsExt; // Locals use crate::utils::path; @@ -112,7 +111,7 @@ impl std::fmt::Display for HostError { /// It provides functions to navigate across the local host file system pub struct Localhost { wrkdir: PathBuf, - files: Vec, + files: Vec, } impl Localhost { @@ -157,7 +156,7 @@ impl Localhost { /// List files in current directory #[allow(dead_code)] - pub fn list_dir(&self) -> Vec { + pub fn list_dir(&self) -> Vec { self.files.clone() } @@ -245,71 +244,68 @@ impl Localhost { } /// Remove file entry - pub fn remove(&mut self, entry: &Entry) -> Result<(), HostError> { - match entry { - Entry::Directory(dir) => { - // If file doesn't exist; return error - debug!("Removing directory {}", dir.path.display()); - if !dir.path.as_path().exists() { - error!("Directory doesn't exist"); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - dir.path.as_path(), - )); + pub fn remove(&mut self, entry: &File) -> Result<(), HostError> { + if entry.is_dir() { + // If file doesn't exist; return error + debug!("Removing directory {}", entry.path().display()); + if !entry.path().exists() { + error!("Directory doesn't exist"); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + entry.path(), + )); + } + // Remove + match std::fs::remove_dir_all(entry.path()) { + Ok(_) => { + // Update dir + self.files = self.scan_dir(self.wrkdir.as_path())?; + info!("Removed directory {}", entry.path().display()); + Ok(()) } - // Remove - match std::fs::remove_dir_all(dir.path.as_path()) { - Ok(_) => { - // Update dir - self.files = self.scan_dir(self.wrkdir.as_path())?; - info!("Removed directory {}", dir.path.display()); - Ok(()) - } - Err(err) => { - error!("Could not remove directory: {}", err); - Err(HostError::new( - HostErrorType::DeleteFailed, - Some(err), - dir.path.as_path(), - )) - } + Err(err) => { + error!("Could not remove directory: {}", err); + Err(HostError::new( + HostErrorType::DeleteFailed, + Some(err), + entry.path(), + )) } } - Entry::File(file) => { - // If file doesn't exist; return error - debug!("Removing file {}", file.path.display()); - if !file.path.as_path().exists() { - error!("File doesn't exist"); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - file.path.as_path(), - )); + } else { + // If file doesn't exist; return error + debug!("Removing file {}", entry.path().display()); + if !entry.path().exists() { + error!("File doesn't exist"); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + entry.path(), + )); + } + // Remove + match std::fs::remove_file(entry.path()) { + Ok(_) => { + // Update dir + self.files = self.scan_dir(self.wrkdir.as_path())?; + info!("Removed file {}", entry.path().display()); + Ok(()) } - // Remove - match std::fs::remove_file(file.path.as_path()) { - Ok(_) => { - // Update dir - self.files = self.scan_dir(self.wrkdir.as_path())?; - info!("Removed file {}", file.path.display()); - Ok(()) - } - Err(err) => { - error!("Could not remove file: {}", err); - Err(HostError::new( - HostErrorType::DeleteFailed, - Some(err), - file.path.as_path(), - )) - } + Err(err) => { + error!("Could not remove file: {}", err); + Err(HostError::new( + HostErrorType::DeleteFailed, + Some(err), + entry.path(), + )) } } } } /// Rename file or directory to new name - pub fn rename(&mut self, entry: &Entry, dst_path: &Path) -> Result<(), HostError> { + pub fn rename(&mut self, entry: &File, dst_path: &Path) -> Result<(), HostError> { match std::fs::rename(entry.path(), dst_path) { Ok(_) => { // Scan dir @@ -338,7 +334,7 @@ impl Localhost { } /// Copy file to destination path - pub fn copy(&mut self, entry: &Entry, dst: &Path) -> Result<(), HostError> { + pub fn copy(&mut self, entry: &File, dst: &Path) -> Result<(), HostError> { // Get absolute path of dest let dst: PathBuf = self.to_path(dst); info!( @@ -347,46 +343,43 @@ impl Localhost { dst.display() ); // Match entry - match entry { - Entry::File(file) => { - // Copy file - // If destination path is a directory, push file name - let dst: PathBuf = match dst.as_path().is_dir() { - true => { - let mut p: PathBuf = dst.clone(); - p.push(file.name.as_str()); - p - } - false => dst.clone(), - }; - // Copy entry path to dst path - if let Err(err) = std::fs::copy(file.path.as_path(), dst.as_path()) { - error!("Failed to copy file: {}", err); - return Err(HostError::new( - HostErrorType::CouldNotCreateFile, - Some(err), - file.path.as_path(), - )); - } - info!("File copied"); + if entry.is_dir() { + // If destination path doesn't exist, create destination + if !dst.exists() { + debug!("Directory {} doesn't exist; creating it", dst.display()); + self.mkdir(dst.as_path())?; } - Entry::Directory(dir) => { - // If destination path doesn't exist, create destination - if !dst.exists() { - debug!("Directory {} doesn't exist; creating it", dst.display()); - self.mkdir(dst.as_path())?; - } - // Scan dir - let dir_files: Vec = self.scan_dir(dir.path.as_path())?; - // Iterate files - for dir_entry in dir_files.iter() { - // Calculate dst - let mut sub_dst: PathBuf = dst.clone(); - sub_dst.push(dir_entry.name()); - // Call function recursively - self.copy(dir_entry, sub_dst.as_path())?; - } + // Scan dir + let dir_files: Vec = self.scan_dir(entry.path())?; + // Iterate files + for dir_entry in dir_files.iter() { + // Calculate dst + let mut sub_dst: PathBuf = dst.clone(); + sub_dst.push(dir_entry.name()); + // Call function recursively + self.copy(dir_entry, sub_dst.as_path())?; } + } else { + // Copy file + // If destination path is a directory, push file name + let dst: PathBuf = match dst.as_path().is_dir() { + true => { + let mut p: PathBuf = dst.clone(); + p.push(entry.name().as_str()); + p + } + false => dst.clone(), + }; + // Copy entry path to dst path + if let Err(err) = std::fs::copy(entry.path(), dst.as_path()) { + error!("Failed to copy file: {}", err); + return Err(HostError::new( + HostErrorType::CouldNotCreateFile, + Some(err), + entry.path(), + )); + } + info!("File copied"); } // Reload directory if dst is pwd match dst.is_dir() { @@ -412,9 +405,8 @@ impl Localhost { Ok(()) } - /// Stat file and create a Entry - #[cfg(target_family = "unix")] - pub fn stat(&self, path: &Path) -> Result { + /// Stat file and create a File + pub fn stat(&self, path: &Path) -> Result { info!("Stating file {}", path.display()); let path: PathBuf = self.to_path(path); let attr = match fs::metadata(path.as_path()) { @@ -428,90 +420,13 @@ impl Localhost { )); } }; - let name = String::from(path.file_name().unwrap().to_str().unwrap_or("")); + let mut metadata = Metadata::from(attr); + if let Ok(symlink) = fs::read_link(path.as_path()) { + metadata.set_symlink(symlink); + metadata.file_type = FileType::Symlink; + } // Match dir / file - let metadata = Metadata { - atime: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), - ctime: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), - gid: Some(attr.gid()), - mode: Some(UnixPex::from(attr.mode())), - mtime: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), - size: if path.is_dir() { - attr.blksize() - } else { - attr.len() - }, - symlink: fs::read_link(path.as_path()).ok(), - uid: Some(attr.uid()), - }; - Ok(match path.is_dir() { - true => Entry::Directory(Directory { - name, - path, - metadata, - }), - false => { - // Is File - let extension = path - .extension() - .map(|s| String::from(s.to_str().unwrap_or(""))); - Entry::File(File { - name, - path, - extension, - metadata, - }) - } - }) - } - - /// Stat file and create a Entry - #[cfg(target_os = "windows")] - pub fn stat(&self, path: &Path) -> Result { - let path: PathBuf = self.to_path(path); - info!("Stating file {}", path.display()); - let attr = match fs::metadata(path.as_path()) { - Ok(metadata) => metadata, - Err(err) => { - error!("Could not read file metadata: {}", err); - return Err(HostError::new( - HostErrorType::FileNotAccessible, - Some(err), - path.as_path(), - )); - } - }; - let name = String::from(path.file_name().unwrap().to_str().unwrap_or("")); - let metadata = Metadata { - atime: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), - ctime: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), - mtime: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), - size: if path.is_dir() { 0 } else { attr.len() }, - symlink: fs::read_link(path.as_path()).ok(), - uid: None, - gid: None, - mode: None, - }; - // Match dir / file - Ok(match path.is_dir() { - true => Entry::Directory(Directory { - name, - path, - metadata, - }), - false => { - // Is File - let extension = path - .extension() - .map(|s| String::from(s.to_str().unwrap_or(""))); - Entry::File(File { - name, - path, - extension, - metadata, - }) - } - }) + Ok(File { path, metadata }) } /// Execute a command on localhost @@ -644,11 +559,11 @@ impl Localhost { } /// Get content of the current directory as a list of fs entry - pub fn scan_dir(&self, dir: &Path) -> Result, HostError> { + pub fn scan_dir(&self, dir: &Path) -> Result, HostError> { info!("Reading directory {}", dir.display()); match std::fs::read_dir(dir) { Ok(e) => { - let mut fs_entries: Vec = Vec::new(); + let mut fs_entries: Vec = Vec::new(); for entry in e.flatten() { // NOTE: 0.4.1, don't fail if stat for one file fails match self.stat(entry.path().as_path()) { @@ -668,7 +583,7 @@ impl Localhost { /// Find files matching `search` on localhost starting from current directory. Search supports recursive search of course. /// The `search` argument supports wilcards ('*', '?') - pub fn find(&self, search: &str) -> Result, HostError> { + pub fn find(&self, search: &str) -> Result, HostError> { self.iter_search(self.wrkdir.as_path(), &WildMatch::new(search)) } @@ -692,9 +607,9 @@ impl Localhost { /// Recursive call for `find` method. /// Search in current directory for files which match `filter`. /// If a directory is found in current directory, `iter_search` will be called using that dir as argument. - fn iter_search(&self, dir: &Path, filter: &WildMatch) -> Result, HostError> { + fn iter_search(&self, dir: &Path, filter: &WildMatch) -> Result, HostError> { // Scan directory - let mut drained: Vec = Vec::new(); + let mut drained: Vec = Vec::new(); match self.scan_dir(dir) { Err(err) => Err(err), Ok(entries) => { @@ -705,20 +620,16 @@ impl Localhost { - if is file: check if it matches `filter` - if it matches `filter`: push to to filter */ - for entry in entries.iter() { - match entry { - Entry::Directory(dir) => { - // If directory matches; push directory to drained - if filter.matches(dir.name.as_str()) { - drained.push(Entry::Directory(dir.clone())); - } - drained.append(&mut self.iter_search(dir.path.as_path(), filter)?); - } - Entry::File(file) => { - if filter.matches(file.name.as_str()) { - drained.push(Entry::File(file.clone())); - } + for entry in entries.into_iter() { + if entry.is_dir() { + // If directory matches; push directory to drained + let next_path = entry.path().to_path_buf(); + if filter.matches(entry.name().as_str()) { + drained.push(entry); } + drained.append(&mut self.iter_search(next_path.as_path(), filter)?); + } else if filter.matches(entry.name().as_str()) { + drained.push(entry); } } Ok(drained) @@ -902,37 +813,27 @@ mod tests { .is_ok()); // Get dir let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); // Verify files - let file_0: &Entry = files.get(0).unwrap(); - match file_0 { - Entry::File(file_0) => { - if file_0.name == String::from("foo.txt") { - assert!(file_0.metadata.symlink.is_none()); - } else { - assert_eq!( - file_0.metadata.symlink.as_ref().unwrap(), - &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) - ); - } - } - _ => panic!("expected entry 0 to be file: {:?}", file_0), - }; + let file_0: &File = files.get(0).unwrap(); + if file_0.name() == String::from("foo.txt") { + assert!(file_0.metadata.symlink.is_none()); + } else { + assert_eq!( + file_0.metadata.symlink.as_ref().unwrap(), + &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) + ); + } // Verify simlink - let file_1: &Entry = files.get(1).unwrap(); - match file_1 { - Entry::File(file_1) => { - if file_1.name == String::from("bar.txt") { - assert_eq!( - file_1.metadata.symlink.as_ref().unwrap(), - &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) - ); - } else { - assert!(file_1.metadata.symlink.is_none()); - } - } - _ => panic!("expected entry 0 to be file: {:?}", file_1), - }; + let file_1: &File = files.get(1).unwrap(); + if file_1.name() == String::from("bar.txt") { + assert_eq!( + file_1.metadata.symlink.as_ref().unwrap(), + &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) + ); + } else { + assert!(file_1.metadata.symlink.is_none()); + } } #[test] @@ -940,10 +841,10 @@ mod tests { fn test_host_localhost_mkdir() { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 0); // There should be 0 files now assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now // Try to re-create directory assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err()); @@ -967,17 +868,17 @@ mod tests { // Create sample file assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok()); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now // Remove file assert!(host.remove(files.get(0).unwrap()).is_ok()); // There should be 0 files now - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 0); // There should be 0 files now // Create directory assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); // Delete directory - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now assert!(host.remove(files.get(0).unwrap()).is_ok()); // Remove unexisting directory @@ -998,7 +899,7 @@ mod tests { PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str()); assert!(StdFile::create(src_path.as_path()).is_ok()); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.get(0).unwrap().name(), "foo.txt"); // Rename file @@ -1008,7 +909,7 @@ mod tests { .rename(files.get(0).unwrap(), dst_path.as_path()) .is_ok()); // There should be still 1 file now, but named bar.txt - let files: Vec = host.list_dir(); + let files: Vec = host.list_dir(); assert_eq!(files.len(), 1); // There should be 0 files now assert_eq!(files.get(0).unwrap().name(), "bar.txt"); // Fail @@ -1052,7 +953,7 @@ mod tests { file2_path.push("bar.txt"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: Entry = host.files.get(0).unwrap().clone(); + let file1_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); @@ -1081,7 +982,7 @@ mod tests { let file2_path: PathBuf = PathBuf::from("bar.txt"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: Entry = host.files.get(0).unwrap().clone(); + let file1_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); @@ -1108,7 +1009,7 @@ mod tests { dir_dest.push("test_dest_dir/"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let dir_src_entry: Entry = host.files.get(0).unwrap().clone(); + let dir_src_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(dir_src_entry.name(), String::from("test_dir")); // Copy assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); @@ -1138,7 +1039,7 @@ mod tests { let dir_dest: PathBuf = PathBuf::from("test_dest_dir/"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let dir_src_entry: Entry = host.files.get(0).unwrap().clone(); + let dir_src_entry: File = host.files.get(0).unwrap().clone(); assert_eq!(dir_src_entry.name(), String::from("test_dir")); // Copy assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); @@ -1178,8 +1079,8 @@ mod tests { assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok()); let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); // Find txt files - let mut result: Vec = host.find("*.txt").ok().unwrap(); - result.sort_by_key(|x: &Entry| x.name().to_lowercase()); + let mut result: Vec = host.find("*.txt").ok().unwrap(); + result.sort_by_key(|x: &File| x.name().to_lowercase()); // There should be 3 entries assert_eq!(result.len(), 3); // Check names (they should be sorted alphabetically already; NOTE: examples/ comes before pippo.txt) @@ -1187,8 +1088,8 @@ mod tests { assert_eq!(result[1].name(), "omar.txt"); assert_eq!(result[2].name(), "pippo.txt"); // Search for directory - let mut result: Vec = host.find("examples*").ok().unwrap(); - result.sort_by_key(|x: &Entry| x.name().to_lowercase()); + let mut result: Vec = host.find("examples*").ok().unwrap(); + result.sort_by_key(|x: &File| x.name().to_lowercase()); assert_eq!(result.len(), 2); assert_eq!(result[0].name(), "examples"); assert_eq!(result[1].name(), "examples.csv"); diff --git a/src/system/sshkey_storage.rs b/src/system/sshkey_storage.rs index a67e244..d25b743 100644 --- a/src/system/sshkey_storage.rs +++ b/src/system/sshkey_storage.rs @@ -28,7 +28,7 @@ // Locals use super::config_client::ConfigClient; // Ext -use remotefs::client::ssh::SshKeyStorage as SshKeyStorageT; +use remotefs_ssh::SshKeyStorage as SshKeyStorageT; use std::collections::HashMap; use std::path::{Path, PathBuf}; diff --git a/src/ui/activities/filetransfer/actions/change_dir.rs b/src/ui/activities/filetransfer/actions/change_dir.rs index bc4e6f7..941a672 100644 --- a/src/ui/activities/filetransfer/actions/change_dir.rs +++ b/src/ui/activities/filetransfer/actions/change_dir.rs @@ -28,7 +28,7 @@ // locals use super::{FileExplorerTab, FileTransferActivity, LogLevel, Msg, PendingActionMsg}; -use remotefs::Directory; +use remotefs::File; use std::path::PathBuf; /// Describes destination for sync browsing @@ -40,18 +40,18 @@ enum SyncBrowsingDestination { impl FileTransferActivity { /// Enter a directory on local host from entry - pub(crate) fn action_enter_local_dir(&mut self, dir: Directory) { - self.local_changedir(dir.path.as_path(), true); + pub(crate) fn action_enter_local_dir(&mut self, dir: File) { + self.local_changedir(dir.path(), true); if self.browser.sync_browsing && self.browser.found().is_none() { - self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name)); + self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name())); } } /// Enter a directory on local host from entry - pub(crate) fn action_enter_remote_dir(&mut self, dir: Directory) { - self.remote_changedir(dir.path.as_path(), true); + pub(crate) fn action_enter_remote_dir(&mut self, dir: File) { + self.remote_changedir(dir.path(), true); if self.browser.sync_browsing && self.browser.found().is_none() { - self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name)); + self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name())); } } diff --git a/src/ui/activities/filetransfer/actions/copy.rs b/src/ui/activities/filetransfer/actions/copy.rs index 7674b9a..209620d 100644 --- a/src/ui/activities/filetransfer/actions/copy.rs +++ b/src/ui/activities/filetransfer/actions/copy.rs @@ -26,20 +26,20 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload}; +use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload}; -use remotefs::{Entry, RemoteErrorType}; +use remotefs::{File, RemoteErrorType}; use std::path::{Path, PathBuf}; impl FileTransferActivity { /// Copy file on local pub(crate) fn action_local_copy(&mut self, input: String) { match self.get_local_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); self.local_copy_file(&entry, dest_path.as_path()); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files @@ -49,18 +49,18 @@ impl FileTransferActivity { self.local_copy_file(entry, dest_path.as_path()); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } /// Copy file on remote pub(crate) fn action_remote_copy(&mut self, input: String) { match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); self.remote_copy_file(entry, dest_path.as_path()); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files @@ -70,11 +70,11 @@ impl FileTransferActivity { self.remote_copy_file(entry, dest_path.as_path()); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } - fn local_copy_file(&mut self, entry: &Entry, dest: &Path) { + fn local_copy_file(&mut self, entry: &File, dest: &Path) { match self.host.copy(entry, dest) { Ok(_) => { self.log( @@ -98,7 +98,7 @@ impl FileTransferActivity { } } - fn remote_copy_file(&mut self, entry: Entry, dest: &Path) { + fn remote_copy_file(&mut self, entry: File, dest: &Path) { match self.client.as_mut().copy(entry.path(), dest) { Ok(_) => { self.log( @@ -129,123 +129,121 @@ impl FileTransferActivity { } /// Tricky copy will be used whenever copy command is not available on remote host - pub(super) fn tricky_copy(&mut self, entry: Entry, dest: &Path) -> Result<(), String> { + pub(super) fn tricky_copy(&mut self, entry: File, dest: &Path) -> Result<(), String> { // NOTE: VERY IMPORTANT; wait block must be umounted or something really bad will happen self.umount_wait(); // match entry - match entry { - Entry::File(entry) => { - // Create tempfile - let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { - Ok(f) => f, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Copy failed: could not create temporary file: {}", err), - ); - return Err(String::from("Could not create temporary file")); - } - }; - // Download file - let name = entry.name.clone(); - let entry_path = entry.path.clone(); - if let Err(err) = - self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name)) - { + if entry.is_dir() { + let tempdir: tempfile::TempDir = match tempfile::TempDir::new() { + Ok(d) => d, + Err(err) => { self.log_and_alert( LogLevel::Error, - format!("Copy failed: could not download to temporary file: {}", err), + format!("Copy failed: could not create temporary directory: {}", err), ); - return Err(err); + return Err(err.to_string()); } - // Get local fs entry - let tmpfile_entry = match self.host.stat(tmpfile.path()) { - Ok(e) => e.unwrap_file(), - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Copy failed: could not stat \"{}\": {}", - tmpfile.path().display(), - err - ), - ); - return Err(err.to_string()); - } - }; - // Upload file to destination - let wrkdir = self.remote().wrkdir.clone(); - if let Err(err) = self.filetransfer_send( - TransferPayload::File(tmpfile_entry), - wrkdir.as_path(), - Some(String::from(dest.to_string_lossy())), - ) { + }; + // Get path of dest + let mut tempdir_path: PathBuf = tempdir.path().to_path_buf(); + tempdir_path.push(entry.name()); + // Download file + if let Err(err) = + self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None) + { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: failed to download file: {}", err), + ); + return Err(err); + } + // Stat dir + let tempdir_entry = match self.host.stat(tempdir_path.as_path()) { + Ok(e) => e, + Err(err) => { self.log_and_alert( LogLevel::Error, format!( - "Copy failed: could not write file {}: {}", - entry_path.display(), + "Copy failed: could not stat \"{}\": {}", + tempdir.path().display(), err ), ); - return Err(err); + return Err(err.to_string()); } - Ok(()) + }; + // Upload to destination + let wrkdir: PathBuf = self.remote().wrkdir.clone(); + if let Err(err) = self.filetransfer_send( + TransferPayload::Any(tempdir_entry), + wrkdir.as_path(), + Some(String::from(dest.to_string_lossy())), + ) { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: failed to send file: {}", err), + ); + return Err(err); } - Entry::Directory(_) => { - let tempdir: tempfile::TempDir = match tempfile::TempDir::new() { - Ok(d) => d, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!("Copy failed: could not create temporary directory: {}", err), - ); - return Err(err.to_string()); - } - }; - // Get path of dest - let mut tempdir_path: PathBuf = tempdir.path().to_path_buf(); - tempdir_path.push(entry.name()); - // Download file - if let Err(err) = - self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None) - { + Ok(()) + } else { + // Create tempfile + let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { + Ok(f) => f, + Err(err) => { self.log_and_alert( LogLevel::Error, - format!("Copy failed: failed to download file: {}", err), + format!("Copy failed: could not create temporary file: {}", err), ); - return Err(err); + return Err(String::from("Could not create temporary file")); } - // Stat dir - let tempdir_entry = match self.host.stat(tempdir_path.as_path()) { - Ok(e) => e, - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Copy failed: could not stat \"{}\": {}", - tempdir.path().display(), - err - ), - ); - return Err(err.to_string()); - } - }; - // Upload to destination - let wrkdir: PathBuf = self.remote().wrkdir.clone(); - if let Err(err) = self.filetransfer_send( - TransferPayload::Any(tempdir_entry), - wrkdir.as_path(), - Some(String::from(dest.to_string_lossy())), - ) { + }; + // Download file + let name = entry.name(); + let entry_path = entry.path().to_path_buf(); + if let Err(err) = + self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name)) + { + self.log_and_alert( + LogLevel::Error, + format!("Copy failed: could not download to temporary file: {}", err), + ); + return Err(err); + } + // Get local fs entry + let tmpfile_entry = match self.host.stat(tmpfile.path()) { + Ok(e) if e.is_file() => e, + Ok(_) => panic!("{} is not a file", tmpfile.path().display()), + Err(err) => { self.log_and_alert( LogLevel::Error, - format!("Copy failed: failed to send file: {}", err), + format!( + "Copy failed: could not stat \"{}\": {}", + tmpfile.path().display(), + err + ), ); - return Err(err); + return Err(err.to_string()); } - Ok(()) + }; + // Upload file to destination + let wrkdir = self.remote().wrkdir.clone(); + if let Err(err) = self.filetransfer_send( + TransferPayload::File(tmpfile_entry), + wrkdir.as_path(), + Some(String::from(dest.to_string_lossy())), + ) { + self.log_and_alert( + LogLevel::Error, + format!( + "Copy failed: could not write file {}: {}", + entry_path.display(), + err + ), + ); + return Err(err); } + Ok(()) } } } diff --git a/src/ui/activities/filetransfer/actions/delete.rs b/src/ui/activities/filetransfer/actions/delete.rs index 1890791..9121dc8 100644 --- a/src/ui/activities/filetransfer/actions/delete.rs +++ b/src/ui/activities/filetransfer/actions/delete.rs @@ -26,46 +26,46 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, LogLevel, SelectedEntry}; +use super::{FileTransferActivity, LogLevel, SelectedFile}; -use remotefs::Entry; +use remotefs::File; impl FileTransferActivity { pub(crate) fn action_local_delete(&mut self) { match self.get_local_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { // Delete file self.local_remove_file(&entry); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Iter files for entry in entries.iter() { // Delete file self.local_remove_file(entry); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } pub(crate) fn action_remote_delete(&mut self) { match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { // Delete file self.remote_remove_file(&entry); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Iter files for entry in entries.iter() { // Delete file self.remote_remove_file(entry); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } - pub(crate) fn local_remove_file(&mut self, entry: &Entry) { + pub(crate) fn local_remove_file(&mut self, entry: &File) { match self.host.remove(entry) { Ok(_) => { // Log @@ -87,7 +87,7 @@ impl FileTransferActivity { } } - pub(crate) fn remote_remove_file(&mut self, entry: &Entry) { + pub(crate) fn remote_remove_file(&mut self, entry: &File) { match self.client.remove_dir_all(entry.path()) { Ok(_) => { self.log( diff --git a/src/ui/activities/filetransfer/actions/edit.rs b/src/ui/activities/filetransfer/actions/edit.rs index e9e377f..efe7827 100644 --- a/src/ui/activities/filetransfer/actions/edit.rs +++ b/src/ui/activities/filetransfer/actions/edit.rs @@ -26,10 +26,10 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload}; +use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload}; // ext -use remotefs::{Entry, File}; +use remotefs::File; use std::fs::OpenOptions; use std::io::Read; use std::path::{Path, PathBuf}; @@ -37,10 +37,10 @@ use std::time::SystemTime; impl FileTransferActivity { pub(crate) fn action_edit_local_file(&mut self) { - let entries: Vec = match self.get_local_selected_entries() { - SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Many(entries) => entries, - SelectedEntry::None => vec![], + let entries: Vec = match self.get_local_selected_entries() { + SelectedFile::One(entry) => vec![entry], + SelectedFile::Many(entries) => entries, + SelectedFile::None => vec![], }; // Edit all entries for entry in entries.iter() { @@ -59,21 +59,21 @@ impl FileTransferActivity { } pub(crate) fn action_edit_remote_file(&mut self) { - let entries: Vec = match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Many(entries) => entries, - SelectedEntry::None => vec![], + let entries: Vec = match self.get_remote_selected_entries() { + SelectedFile::One(entry) => vec![entry], + SelectedFile::Many(entries) => entries, + SelectedFile::None => vec![], }; // Edit all entries for entry in entries.into_iter() { // Check if file - if let Entry::File(file) = entry { + if entry.is_file() { self.log( LogLevel::Info, - format!("Opening file \"{}\"…", file.path.display()), + format!("Opening file \"{}\"…", entry.path().display()), ); // Edit file - if let Err(err) = self.edit_remote_file(file) { + if let Err(err) = self.edit_remote_file(entry) { self.log_and_alert(LogLevel::Error, err); } } @@ -149,8 +149,8 @@ impl FileTransferActivity { Err(err) => return Err(err), }; // Download file - let file_name = file.name.clone(); - let file_path = file.path.clone(); + let file_name = file.name(); + let file_path = file.path().to_path_buf(); if let Err(err) = self.filetransfer_recv( TransferPayload::File(file), tmpfile.as_path(), @@ -160,7 +160,7 @@ impl FileTransferActivity { } // Get current file modification time let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) { - Ok(e) => e.metadata().mtime, + Ok(e) => e.metadata().modified.unwrap_or(std::time::UNIX_EPOCH), Err(err) => { return Err(format!( "Could not stat \"{}\": {}", @@ -174,7 +174,7 @@ impl FileTransferActivity { return Err(err); } // Get local fs entry - let tmpfile_entry: Entry = match self.host.stat(tmpfile.as_path()) { + let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) { Ok(e) => e, Err(err) => { return Err(format!( @@ -185,7 +185,12 @@ impl FileTransferActivity { } }; // Check if file has changed - match prev_mtime != tmpfile_entry.metadata().mtime { + match prev_mtime + != tmpfile_entry + .metadata() + .modified + .unwrap_or(std::time::UNIX_EPOCH) + { true => { self.log( LogLevel::Info, @@ -196,7 +201,7 @@ impl FileTransferActivity { ); // Get local fs entry let tmpfile_entry = match self.host.stat(tmpfile.as_path()) { - Ok(e) => e.unwrap_file(), + Ok(e) => e, Err(err) => { return Err(format!( "Could not stat \"{}\": {}", diff --git a/src/ui/activities/filetransfer/actions/find.rs b/src/ui/activities/filetransfer/actions/find.rs index c6ffba9..17fab66 100644 --- a/src/ui/activities/filetransfer/actions/find.rs +++ b/src/ui/activities/filetransfer/actions/find.rs @@ -27,19 +27,19 @@ */ // locals use super::super::browser::FileExplorerTab; -use super::{Entry, FileTransferActivity, LogLevel, SelectedEntry, TransferOpts, TransferPayload}; +use super::{File, FileTransferActivity, LogLevel, SelectedFile, TransferOpts, TransferPayload}; use std::path::PathBuf; impl FileTransferActivity { - pub(crate) fn action_local_find(&mut self, input: String) -> Result, String> { + pub(crate) fn action_local_find(&mut self, input: String) -> Result, String> { match self.host.find(input.as_str()) { Ok(entries) => Ok(entries), Err(err) => Err(format!("Could not search for files: {}", err)), } } - pub(crate) fn action_remote_find(&mut self, input: String) -> Result, String> { + pub(crate) fn action_remote_find(&mut self, input: String) -> Result, String> { match self.client.as_mut().find(input.as_str()) { Ok(entries) => Ok(entries), Err(err) => Err(format!("Could not search for files: {}", err)), @@ -48,14 +48,15 @@ impl FileTransferActivity { pub(crate) fn action_find_changedir(&mut self) { // Match entry - if let SelectedEntry::One(entry) = self.get_found_selected_entries() { + if let SelectedFile::One(entry) = self.get_found_selected_entries() { // Get path: if a directory, use directory path; if it is a File, get parent path - let path: PathBuf = match entry { - Entry::Directory(dir) => dir.path, - Entry::File(file) => match file.path.parent() { + let path = if entry.is_dir() { + entry.path().to_path_buf() + } else { + match entry.path().parent() { None => PathBuf::from("."), Some(p) => p.to_path_buf(), - }, + } }; // Change directory match self.browser.tab() { @@ -75,13 +76,13 @@ impl FileTransferActivity { FileExplorerTab::FindRemote | FileExplorerTab::Remote => self.local().wrkdir.clone(), }; match self.get_found_selected_entries() { - SelectedEntry::One(entry) => match self.browser.tab() { + SelectedFile::One(entry) => match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref()); if self.config().get_prompt_on_file_replace() && self.remote_file_exists(file_to_check.as_path()) && !self.should_replace_file( - opts.save_as.as_deref().unwrap_or_else(|| entry.name()), + opts.save_as.clone().unwrap_or_else(|| entry.name()), ) { // Do not replace @@ -103,7 +104,7 @@ impl FileTransferActivity { if self.config().get_prompt_on_file_replace() && self.local_file_exists(file_to_check.as_path()) && !self.should_replace_file( - opts.save_as.as_deref().unwrap_or_else(|| entry.name()), + opts.save_as.clone().unwrap_or_else(|| entry.name()), ) { // Do not replace @@ -121,7 +122,7 @@ impl FileTransferActivity { } } }, - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // In case of selection: save multiple files in wrkdir/input let mut dest_path: PathBuf = wrkdir; if let Some(save_as) = opts.save_as { @@ -132,7 +133,7 @@ impl FileTransferActivity { FileExplorerTab::FindLocal | FileExplorerTab::Local => { if self.config().get_prompt_on_file_replace() { // Check which file would be replaced - let existing_files: Vec<&Entry> = entries + let existing_files: Vec<&File> = entries .iter() .filter(|x| { self.remote_file_exists( @@ -163,7 +164,7 @@ impl FileTransferActivity { FileExplorerTab::FindRemote | FileExplorerTab::Remote => { if self.config().get_prompt_on_file_replace() { // Check which file would be replaced - let existing_files: Vec<&Entry> = entries + let existing_files: Vec<&File> = entries .iter() .filter(|x| { self.local_file_exists( @@ -191,28 +192,28 @@ impl FileTransferActivity { } } } - SelectedEntry::None => {} + SelectedFile::None => {} } } pub(crate) fn action_find_delete(&mut self) { match self.get_found_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { // Delete file self.remove_found_file(&entry); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Iter files for entry in entries.iter() { // Delete file self.remove_found_file(entry); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } - fn remove_found_file(&mut self, entry: &Entry) { + fn remove_found_file(&mut self, entry: &File) { match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { self.local_remove_file(entry); @@ -225,39 +226,39 @@ impl FileTransferActivity { pub(crate) fn action_find_open(&mut self) { match self.get_found_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { // Open file self.open_found_file(&entry, None); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Iter files for entry in entries.iter() { // Open file self.open_found_file(entry, None); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } pub(crate) fn action_find_open_with(&mut self, with: &str) { match self.get_found_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { // Open file self.open_found_file(&entry, Some(with)); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Iter files for entry in entries.iter() { // Open file self.open_found_file(entry, Some(with)); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } - fn open_found_file(&mut self, entry: &Entry, with: Option<&str>) { + fn open_found_file(&mut self, entry: &File, with: Option<&str>) { match self.browser.tab() { FileExplorerTab::FindLocal | FileExplorerTab::Local => { self.action_open_local_file(entry, with); diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index e3b7350..a814336 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -29,7 +29,7 @@ pub(self) use super::{ browser::FileExplorerTab, FileTransferActivity, Id, LogLevel, Msg, PendingActionMsg, TransferOpts, TransferPayload, }; -pub(self) use remotefs::Entry; +pub(self) use remotefs::File; use tuirealm::{State, StateValue}; // actions @@ -49,100 +49,100 @@ pub(crate) mod submit; pub(crate) mod symlink; #[derive(Debug)] -pub(crate) enum SelectedEntry { - One(Entry), - Many(Vec), +pub(crate) enum SelectedFile { + One(File), + Many(Vec), None, } #[derive(Debug)] -enum SelectedEntryIndex { +enum SelectedFileIndex { One(usize), Many(Vec), None, } -impl From> for SelectedEntry { - fn from(opt: Option<&Entry>) -> Self { +impl From> for SelectedFile { + fn from(opt: Option<&File>) -> Self { match opt { - Some(e) => SelectedEntry::One(e.clone()), - None => SelectedEntry::None, + Some(e) => SelectedFile::One(e.clone()), + None => SelectedFile::None, } } } -impl From> for SelectedEntry { - fn from(files: Vec<&Entry>) -> Self { - SelectedEntry::Many(files.into_iter().cloned().collect()) +impl From> for SelectedFile { + fn from(files: Vec<&File>) -> Self { + SelectedFile::Many(files.into_iter().cloned().collect()) } } impl FileTransferActivity { /// Get local file entry - pub(crate) fn get_local_selected_entries(&self) -> SelectedEntry { + pub(crate) fn get_local_selected_entries(&self) -> SelectedFile { match self.get_selected_index(&Id::ExplorerLocal) { - SelectedEntryIndex::One(idx) => SelectedEntry::from(self.local().get(idx)), - SelectedEntryIndex::Many(files) => { - let files: Vec<&Entry> = files + SelectedFileIndex::One(idx) => SelectedFile::from(self.local().get(idx)), + SelectedFileIndex::Many(files) => { + let files: Vec<&File> = files .iter() - .map(|x| self.local().get(*x)) // Usize to Option + .map(|x| self.local().get(*x)) // Usize to Option .flatten() .collect(); - SelectedEntry::from(files) + SelectedFile::from(files) } - SelectedEntryIndex::None => SelectedEntry::None, + SelectedFileIndex::None => SelectedFile::None, } } /// Get remote file entry - pub(crate) fn get_remote_selected_entries(&self) -> SelectedEntry { + pub(crate) fn get_remote_selected_entries(&self) -> SelectedFile { match self.get_selected_index(&Id::ExplorerRemote) { - SelectedEntryIndex::One(idx) => SelectedEntry::from(self.remote().get(idx)), - SelectedEntryIndex::Many(files) => { - let files: Vec<&Entry> = files + SelectedFileIndex::One(idx) => SelectedFile::from(self.remote().get(idx)), + SelectedFileIndex::Many(files) => { + let files: Vec<&File> = files .iter() - .map(|x| self.remote().get(*x)) // Usize to Option + .map(|x| self.remote().get(*x)) // Usize to Option .flatten() .collect(); - SelectedEntry::from(files) + SelectedFile::from(files) } - SelectedEntryIndex::None => SelectedEntry::None, + SelectedFileIndex::None => SelectedFile::None, } } /// Returns whether only one entry is selected on local host pub(crate) fn is_local_selected_one(&self) -> bool { - matches!(self.get_local_selected_entries(), SelectedEntry::One(_)) + matches!(self.get_local_selected_entries(), SelectedFile::One(_)) } /// Returns whether only one entry is selected on remote host pub(crate) fn is_remote_selected_one(&self) -> bool { - matches!(self.get_remote_selected_entries(), SelectedEntry::One(_)) + matches!(self.get_remote_selected_entries(), SelectedFile::One(_)) } /// Get remote file entry - pub(crate) fn get_found_selected_entries(&self) -> SelectedEntry { + pub(crate) fn get_found_selected_entries(&self) -> SelectedFile { match self.get_selected_index(&Id::ExplorerFind) { - SelectedEntryIndex::One(idx) => { - SelectedEntry::from(self.found().as_ref().unwrap().get(idx)) + SelectedFileIndex::One(idx) => { + SelectedFile::from(self.found().as_ref().unwrap().get(idx)) } - SelectedEntryIndex::Many(files) => { - let files: Vec<&Entry> = files + SelectedFileIndex::Many(files) => { + let files: Vec<&File> = files .iter() - .map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option + .map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option .flatten() .collect(); - SelectedEntry::from(files) + SelectedFile::from(files) } - SelectedEntryIndex::None => SelectedEntry::None, + SelectedFileIndex::None => SelectedFile::None, } } // -- private - fn get_selected_index(&self, id: &Id) -> SelectedEntryIndex { + fn get_selected_index(&self, id: &Id) -> SelectedFileIndex { match self.app.state(id) { - Ok(State::One(StateValue::Usize(idx))) => SelectedEntryIndex::One(idx), + Ok(State::One(StateValue::Usize(idx))) => SelectedFileIndex::One(idx), Ok(State::Vec(files)) => { let list: Vec = files .iter() @@ -151,9 +151,9 @@ impl FileTransferActivity { _ => 0, }) .collect(); - SelectedEntryIndex::Many(list) + SelectedFileIndex::Many(list) } - _ => SelectedEntryIndex::None, + _ => SelectedFileIndex::None, } } } diff --git a/src/ui/activities/filetransfer/actions/newfile.rs b/src/ui/activities/filetransfer/actions/newfile.rs index c8119e4..29d95da 100644 --- a/src/ui/activities/filetransfer/actions/newfile.rs +++ b/src/ui/activities/filetransfer/actions/newfile.rs @@ -26,8 +26,8 @@ * SOFTWARE. */ // locals -use super::{Entry, FileTransferActivity, LogLevel}; -use std::fs::File; +use super::{File, FileTransferActivity, LogLevel}; +use std::fs::File as StdFile; use std::path::PathBuf; impl FileTransferActivity { @@ -86,7 +86,7 @@ impl FileTransferActivity { ), Ok(tfile) => { // Stat tempfile - let local_file: Entry = match self.host.stat(tfile.path()) { + let local_file: File = match self.host.stat(tfile.path()) { Err(err) => { self.log_and_alert( LogLevel::Error, @@ -96,9 +96,9 @@ impl FileTransferActivity { } Ok(f) => f, }; - if let Entry::File(local_file) = local_file { + if local_file.is_file() { // Create file - let reader = Box::new(match File::open(tfile.path()) { + let reader = Box::new(match StdFile::open(tfile.path()) { Ok(f) => f, Err(err) => { self.log_and_alert( diff --git a/src/ui/activities/filetransfer/actions/open.rs b/src/ui/activities/filetransfer/actions/open.rs index 4366c4f..5936fc6 100644 --- a/src/ui/activities/filetransfer/actions/open.rs +++ b/src/ui/activities/filetransfer/actions/open.rs @@ -26,17 +26,17 @@ * SOFTWARE. */ // locals -use super::{Entry, FileTransferActivity, LogLevel, SelectedEntry, TransferPayload}; +use super::{File, FileTransferActivity, LogLevel, SelectedFile, TransferPayload}; // ext use std::path::{Path, PathBuf}; impl FileTransferActivity { /// Open local file pub(crate) fn action_open_local(&mut self) { - let entries: Vec = match self.get_local_selected_entries() { - SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Many(entries) => entries, - SelectedEntry::None => vec![], + let entries: Vec = match self.get_local_selected_entries() { + SelectedFile::One(entry) => vec![entry], + SelectedFile::Many(entries) => entries, + SelectedFile::None => vec![], }; entries .iter() @@ -45,10 +45,10 @@ impl FileTransferActivity { /// Open local file pub(crate) fn action_open_remote(&mut self) { - let entries: Vec = match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Many(entries) => entries, - SelectedEntry::None => vec![], + let entries: Vec = match self.get_remote_selected_entries() { + SelectedFile::One(entry) => vec![entry], + SelectedFile::Many(entries) => entries, + SelectedFile::None => vec![], }; entries .iter() @@ -56,20 +56,21 @@ impl FileTransferActivity { } /// Perform open lopcal file - pub(crate) fn action_open_local_file(&mut self, entry: &Entry, open_with: Option<&str>) { + pub(crate) fn action_open_local_file(&mut self, entry: &File, open_with: Option<&str>) { self.open_path_with(entry.path(), open_with); } /// Open remote file. The file is first downloaded to a temporary directory on localhost - pub(crate) fn action_open_remote_file(&mut self, entry: &Entry, open_with: Option<&str>) { + pub(crate) fn action_open_remote_file(&mut self, entry: &File, open_with: Option<&str>) { // Download file - let tmpfile: String = match self.get_cache_tmp_name(entry.name(), entry.extension()) { - None => { - self.log(LogLevel::Error, String::from("Could not create tempdir")); - return; - } - Some(p) => p, - }; + let tmpfile: String = + match self.get_cache_tmp_name(&entry.name(), entry.extension().as_deref()) { + None => { + self.log(LogLevel::Error, String::from("Could not create tempdir")); + return; + } + Some(p) => p, + }; let cache: PathBuf = match self.cache.as_ref() { None => { self.log(LogLevel::Error, String::from("Could not create tempdir")); @@ -101,10 +102,10 @@ impl FileTransferActivity { /// Open selected file with provided application pub(crate) fn action_local_open_with(&mut self, with: &str) { - let entries: Vec = match self.get_local_selected_entries() { - SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Many(entries) => entries, - SelectedEntry::None => vec![], + let entries: Vec = match self.get_local_selected_entries() { + SelectedFile::One(entry) => vec![entry], + SelectedFile::Many(entries) => entries, + SelectedFile::None => vec![], }; // Open all entries entries @@ -114,10 +115,10 @@ impl FileTransferActivity { /// Open selected file with provided application pub(crate) fn action_remote_open_with(&mut self, with: &str) { - let entries: Vec = match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => vec![entry], - SelectedEntry::Many(entries) => entries, - SelectedEntry::None => vec![], + let entries: Vec = match self.get_remote_selected_entries() { + SelectedFile::One(entry) => vec![entry], + SelectedFile::Many(entries) => entries, + SelectedFile::None => vec![], }; // Open all entries entries diff --git a/src/ui/activities/filetransfer/actions/rename.rs b/src/ui/activities/filetransfer/actions/rename.rs index 9bb1cf7..5e095cb 100644 --- a/src/ui/activities/filetransfer/actions/rename.rs +++ b/src/ui/activities/filetransfer/actions/rename.rs @@ -26,7 +26,7 @@ * SOFTWARE. */ // locals -use super::{Entry, FileTransferActivity, LogLevel, SelectedEntry}; +use super::{File, FileTransferActivity, LogLevel, SelectedFile}; use remotefs::RemoteErrorType; use std::path::{Path, PathBuf}; @@ -34,11 +34,11 @@ use std::path::{Path, PathBuf}; impl FileTransferActivity { pub(crate) fn action_local_rename(&mut self, input: String) { match self.get_local_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); self.local_rename_file(&entry, dest_path.as_path()); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files @@ -48,17 +48,17 @@ impl FileTransferActivity { self.local_rename_file(entry, dest_path.as_path()); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } pub(crate) fn action_remote_rename(&mut self, input: String) { match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { let dest_path: PathBuf = PathBuf::from(input); self.remote_rename_file(&entry, dest_path.as_path()); } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // Try to copy each file to Input/{FILE_NAME} let base_path: PathBuf = PathBuf::from(input); // Iter files @@ -68,11 +68,11 @@ impl FileTransferActivity { self.remote_rename_file(entry, dest_path.as_path()); } } - SelectedEntry::None => {} + SelectedFile::None => {} } } - fn local_rename_file(&mut self, entry: &Entry, dest: &Path) { + fn local_rename_file(&mut self, entry: &File, dest: &Path) { match self.host.rename(entry, dest) { Ok(_) => { self.log( @@ -96,7 +96,7 @@ impl FileTransferActivity { } } - fn remote_rename_file(&mut self, entry: &Entry, dest: &Path) { + fn remote_rename_file(&mut self, entry: &File, dest: &Path) { match self.client.as_mut().mov(entry.path(), dest) { Ok(_) => { self.log( @@ -125,7 +125,7 @@ impl FileTransferActivity { /// Tricky move will be used whenever copy command is not available on remote host. /// It basically uses the tricky_copy function, then it just deletes the previous entry (`entry`) - fn tricky_move(&mut self, entry: &Entry, dest: &Path) { + fn tricky_move(&mut self, entry: &File, dest: &Path) { debug!( "Using tricky-move to move entry {} to {}", entry.path().display(), diff --git a/src/ui/activities/filetransfer/actions/save.rs b/src/ui/activities/filetransfer/actions/save.rs index 0b6e70d..425760c 100644 --- a/src/ui/activities/filetransfer/actions/save.rs +++ b/src/ui/activities/filetransfer/actions/save.rs @@ -27,7 +27,7 @@ */ // locals use super::{ - Entry, FileTransferActivity, LogLevel, Msg, PendingActionMsg, SelectedEntry, TransferOpts, + File, FileTransferActivity, LogLevel, Msg, PendingActionMsg, SelectedFile, TransferOpts, TransferPayload, }; use std::path::{Path, PathBuf}; @@ -52,19 +52,18 @@ impl FileTransferActivity { fn local_send_file(&mut self, opts: TransferOpts) { let wrkdir: PathBuf = self.remote().wrkdir.clone(); match self.get_local_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref()); if self.config().get_prompt_on_file_replace() && self.remote_file_exists(file_to_check.as_path()) - && !self.should_replace_file( - opts.save_as.as_deref().unwrap_or_else(|| entry.name()), - ) + && !self + .should_replace_file(opts.save_as.clone().unwrap_or_else(|| entry.name())) { // Do not replace return; } if let Err(err) = self.filetransfer_send( - TransferPayload::Any(entry.clone()), + TransferPayload::Any(entry), wrkdir.as_path(), opts.save_as, ) { @@ -76,7 +75,7 @@ impl FileTransferActivity { } } } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // In case of selection: save multiple files in wrkdir/input let mut dest_path: PathBuf = wrkdir; if let Some(save_as) = opts.save_as { @@ -85,7 +84,7 @@ impl FileTransferActivity { // Iter files if self.config().get_prompt_on_file_replace() { // Check which file would be replaced - let existing_files: Vec<&Entry> = entries + let existing_files: Vec<&File> = entries .iter() .filter(|x| { self.remote_file_exists( @@ -111,25 +110,24 @@ impl FileTransferActivity { } } } - SelectedEntry::None => {} + SelectedFile::None => {} } } fn remote_recv_file(&mut self, opts: TransferOpts) { let wrkdir: PathBuf = self.local().wrkdir.clone(); match self.get_remote_selected_entries() { - SelectedEntry::One(entry) => { + SelectedFile::One(entry) => { let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref()); if self.config().get_prompt_on_file_replace() && self.local_file_exists(file_to_check.as_path()) - && !self.should_replace_file( - opts.save_as.as_deref().unwrap_or_else(|| entry.name()), - ) + && !self + .should_replace_file(opts.save_as.clone().unwrap_or_else(|| entry.name())) { return; } if let Err(err) = self.filetransfer_recv( - TransferPayload::Any(entry.clone()), + TransferPayload::Any(entry), wrkdir.as_path(), opts.save_as, ) { @@ -141,7 +139,7 @@ impl FileTransferActivity { } } } - SelectedEntry::Many(entries) => { + SelectedFile::Many(entries) => { // In case of selection: save multiple files in wrkdir/input let mut dest_path: PathBuf = wrkdir; if let Some(save_as) = opts.save_as { @@ -150,7 +148,7 @@ impl FileTransferActivity { // Iter files if self.config().get_prompt_on_file_replace() { // Check which file would be replaced - let existing_files: Vec<&Entry> = entries + let existing_files: Vec<&File> = entries .iter() .filter(|x| { self.local_file_exists( @@ -176,13 +174,13 @@ impl FileTransferActivity { } } } - SelectedEntry::None => {} + SelectedFile::None => {} } } /// Set pending transfer into storage - pub(crate) fn should_replace_file(&mut self, file_name: &str) -> bool { - self.mount_radio_replace(file_name); + pub(crate) fn should_replace_file(&mut self, file_name: String) -> bool { + self.mount_radio_replace(&file_name); // Wait for answer trace!("Asking user whether he wants to replace file {}", file_name); if self.wait_for_pending_msg(&[ @@ -201,8 +199,8 @@ impl FileTransferActivity { } /// Set pending transfer for many files into storage and mount radio - pub(crate) fn should_replace_files(&mut self, files: Vec<&Entry>) -> bool { - let file_names: Vec<&str> = files.iter().map(|x| x.name()).collect(); + pub(crate) fn should_replace_files(&mut self, files: Vec<&File>) -> bool { + let file_names: Vec = files.iter().map(|x| x.name()).collect(); self.mount_radio_replace_many(file_names.as_slice()); // Wait for answer trace!( @@ -225,14 +223,14 @@ impl FileTransferActivity { } /// Get file to check for path - pub(crate) fn file_to_check(e: &Entry, alt: Option<&String>) -> PathBuf { + pub(crate) fn file_to_check(e: &File, alt: Option<&String>) -> PathBuf { match alt { Some(s) => PathBuf::from(s), None => PathBuf::from(e.name()), } } - pub(crate) fn file_to_check_many(e: &Entry, wrkdir: &Path) -> PathBuf { + pub(crate) fn file_to_check_many(e: &File, wrkdir: &Path) -> PathBuf { let mut p = wrkdir.to_path_buf(); p.push(e.name()); p diff --git a/src/ui/activities/filetransfer/actions/submit.rs b/src/ui/activities/filetransfer/actions/submit.rs index 6aff312..efa3e99 100644 --- a/src/ui/activities/filetransfer/actions/submit.rs +++ b/src/ui/activities/filetransfer/actions/submit.rs @@ -26,9 +26,7 @@ * SOFTWARE. */ // locals -use super::{Entry, FileTransferActivity}; - -use remotefs::fs::{File, Metadata}; +use super::{File, FileTransferActivity}; enum SubmitAction { ChangeDir, @@ -38,73 +36,59 @@ enum SubmitAction { impl FileTransferActivity { /// Decides which action to perform on submit for local explorer /// Return true whether the directory changed - pub(crate) fn action_submit_local(&mut self, entry: Entry) { - let (action, entry) = match &entry { - Entry::Directory(_) => (SubmitAction::ChangeDir, entry), - Entry::File(File { - path, - metadata: - Metadata { - symlink: Some(symlink), - .. - }, - .. - }) => { - // Stat file - let stat_file = match self.host.stat(symlink.as_path()) { - Ok(e) => e, - Err(err) => { - warn!( - "Could not stat file pointed by {} ({}): {}", - path.display(), - symlink.display(), - err - ); - entry - } - }; - (SubmitAction::ChangeDir, stat_file) - } - Entry::File(_) => (SubmitAction::None, entry), + pub(crate) fn action_submit_local(&mut self, entry: File) { + let (action, entry) = if entry.is_dir() { + (SubmitAction::ChangeDir, entry) + } else if entry.metadata().symlink.is_some() { + // Stat file + let symlink = entry.metadata().symlink.as_ref().unwrap(); + let stat_file = match self.host.stat(symlink.as_path()) { + Ok(e) => e, + Err(err) => { + warn!( + "Could not stat file pointed by {} ({}): {}", + entry.path().display(), + symlink.display(), + err + ); + entry + } + }; + (SubmitAction::ChangeDir, stat_file) + } else { + (SubmitAction::None, entry) }; - if let (SubmitAction::ChangeDir, Entry::Directory(dir)) = (action, entry) { - self.action_enter_local_dir(dir) + if let (SubmitAction::ChangeDir, entry) = (action, entry) { + self.action_enter_local_dir(entry) } } /// Decides which action to perform on submit for remote explorer /// Return true whether the directory changed - pub(crate) fn action_submit_remote(&mut self, entry: Entry) { - let (action, entry) = match &entry { - Entry::Directory(_) => (SubmitAction::ChangeDir, entry), - Entry::File(File { - path, - metadata: - Metadata { - symlink: Some(symlink), - .. - }, - .. - }) => { - // Stat file - let stat_file = match self.client.stat(symlink.as_path()) { - Ok(e) => e, - Err(err) => { - warn!( - "Could not stat file pointed by {} ({}): {}", - path.display(), - symlink.display(), - err - ); - entry - } - }; - (SubmitAction::ChangeDir, stat_file) - } - Entry::File(_) => (SubmitAction::None, entry), + pub(crate) fn action_submit_remote(&mut self, entry: File) { + let (action, entry) = if entry.is_dir() { + (SubmitAction::ChangeDir, entry) + } else if entry.metadata().symlink.is_some() { + // Stat file + let symlink = entry.metadata().symlink.as_ref().unwrap(); + let stat_file = match self.client.stat(symlink.as_path()) { + Ok(e) => e, + Err(err) => { + warn!( + "Could not stat file pointed by {} ({}): {}", + entry.path().display(), + symlink.display(), + err + ); + entry + } + }; + (SubmitAction::ChangeDir, stat_file) + } else { + (SubmitAction::None, entry) }; - if let (SubmitAction::ChangeDir, Entry::Directory(dir)) = (action, entry) { - self.action_enter_remote_dir(dir) + if let (SubmitAction::ChangeDir, entry) = (action, entry) { + self.action_enter_remote_dir(entry) } } } diff --git a/src/ui/activities/filetransfer/actions/symlink.rs b/src/ui/activities/filetransfer/actions/symlink.rs index 3006944..04e25a9 100644 --- a/src/ui/activities/filetransfer/actions/symlink.rs +++ b/src/ui/activities/filetransfer/actions/symlink.rs @@ -26,7 +26,7 @@ * SOFTWARE. */ // locals -use super::{FileTransferActivity, LogLevel, SelectedEntry}; +use super::{FileTransferActivity, LogLevel, SelectedFile}; use std::path::PathBuf; @@ -34,7 +34,7 @@ impl FileTransferActivity { /// Create symlink on localhost #[cfg(target_family = "unix")] pub(crate) fn action_local_symlink(&mut self, name: String) { - if let SelectedEntry::One(entry) = self.get_local_selected_entries() { + if let SelectedFile::One(entry) = self.get_local_selected_entries() { match self .host .symlink(PathBuf::from(name.as_str()).as_path(), entry.path()) @@ -66,7 +66,7 @@ impl FileTransferActivity { /// Copy file on remote pub(crate) fn action_remote_symlink(&mut self, name: String) { - if let SelectedEntry::One(entry) = self.get_remote_selected_entries() { + if let SelectedFile::One(entry) = self.get_remote_selected_entries() { match self .client .symlink(PathBuf::from(name.as_str()).as_path(), entry.path()) diff --git a/src/ui/activities/filetransfer/components/popups.rs b/src/ui/activities/filetransfer/components/popups.rs index 17bf242..762ad06 100644 --- a/src/ui/activities/filetransfer/components/popups.rs +++ b/src/ui/activities/filetransfer/components/popups.rs @@ -31,7 +31,8 @@ use crate::explorer::FileSorting; use crate::utils::fmt::fmt_time; use bytesize::ByteSize; -use remotefs::Entry; +use remotefs::File; +use std::time::UNIX_EPOCH; use tui_realm_stdlib::{Input, List, Paragraph, ProgressBar, Radio, Span}; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; @@ -399,7 +400,7 @@ pub struct FileInfoPopup { } impl FileInfoPopup { - pub fn new(file: &Entry) -> Self { + pub fn new(file: &File) -> Self { let mut texts: TableBuilder = TableBuilder::default(); // Abs path let real_path = file.metadata().symlink.as_deref(); @@ -422,9 +423,18 @@ impl FileInfoPopup { .add_row() .add_col(TextSpan::from("Size: ")) .add_col(TextSpan::new(format!("{} ({})", bsize, size).as_str()).fg(Color::Cyan)); - let atime: String = fmt_time(file.metadata().atime, "%b %d %Y %H:%M:%S"); - let ctime: String = fmt_time(file.metadata().ctime, "%b %d %Y %H:%M:%S"); - let mtime: String = fmt_time(file.metadata().mtime, "%b %d %Y %H:%M:%S"); + let atime: String = fmt_time( + file.metadata().accessed.unwrap_or(UNIX_EPOCH), + "%b %d %Y %H:%M:%S", + ); + let ctime: String = fmt_time( + file.metadata().created.unwrap_or(UNIX_EPOCH), + "%b %d %Y %H:%M:%S", + ); + let mtime: String = fmt_time( + file.metadata().modified.unwrap_or(UNIX_EPOCH), + "%b %d %Y %H:%M:%S", + ); texts .add_row() .add_col(TextSpan::from("Creation time: ")) @@ -1373,7 +1383,7 @@ pub struct ReplacingFilesListPopup { } impl ReplacingFilesListPopup { - pub fn new(files: &[&str], color: Color) -> Self { + pub fn new(files: &[String], color: Color) -> Self { Self { component: List::default() .borders( diff --git a/src/ui/activities/filetransfer/lib/browser.rs b/src/ui/activities/filetransfer/lib/browser.rs index 3661d6b..ec3e077 100644 --- a/src/ui/activities/filetransfer/lib/browser.rs +++ b/src/ui/activities/filetransfer/lib/browser.rs @@ -28,7 +28,7 @@ use crate::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSorting, GroupDirs}; use crate::system::config_client::ConfigClient; -use remotefs::Entry; +use remotefs::File; use std::path::Path; /// File explorer tab @@ -92,7 +92,7 @@ impl Browser { self.found.as_mut().map(|x| &mut x.1) } - pub fn set_found(&mut self, tab: FoundExplorerTab, files: Vec, wrkdir: &Path) { + pub fn set_found(&mut self, tab: FoundExplorerTab, files: Vec, wrkdir: &Path) { let mut explorer = Self::build_found_explorer(wrkdir); explorer.set_files(files); self.found = Some((tab, explorer)); diff --git a/src/ui/activities/filetransfer/misc.rs b/src/ui/activities/filetransfer/misc.rs index 836a8e2..5c02a6d 100644 --- a/src/ui/activities/filetransfer/misc.rs +++ b/src/ui/activities/filetransfer/misc.rs @@ -191,7 +191,8 @@ impl FileTransferActivity { TransferPayload::File(file) => { format!( "File \"{}\" has been successfully transferred ({})", - file.name, transfer_stats + file.name(), + transfer_stats ) } TransferPayload::Any(entry) => { diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index 651a158..72a3f80 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -32,7 +32,7 @@ use crate::utils::fmt::fmt_millis; // Ext use bytesize::ByteSize; -use remotefs::fs::{Entry, File, UnixPex, Welcome}; +use remotefs::fs::{File, ReadStream, UnixPex, Welcome, WriteStream}; use remotefs::{RemoteError, RemoteErrorType}; use std::fs::File as StdFile; use std::io::{Read, Seek, Write}; @@ -59,13 +59,13 @@ enum TransferErrorReason { /// Represents the entity to send or receive during a transfer. /// - File: describes an individual `File` to send -/// - Any: Can be any kind of `Entry`, but just one -/// - Many: a list of `Entry` +/// - Any: Can be any kind of `File`, but just one +/// - Many: a list of `File` #[derive(Debug)] pub(super) enum TransferPayload { File(File), - Any(Entry), - Many(Vec), + Any(File), + Many(Vec), } impl FileTransferActivity { @@ -224,7 +224,7 @@ impl FileTransferActivity { // Mount progress bar self.mount_progress_bar(format!("Uploading {}…", file.path.display())); // Get remote path - let file_name: String = file.name.clone(); + let file_name: String = file.name(); let mut remote_path: PathBuf = PathBuf::from(curr_remote_path); let remote_file_name: PathBuf = match dst_name { Some(s) => PathBuf::from(s.as_str()), @@ -242,7 +242,7 @@ impl FileTransferActivity { /// Send a `TransferPayload` of type `Any` fn filetransfer_send_any( &mut self, - entry: &Entry, + entry: &File, curr_remote_path: &Path, dst_name: Option, ) -> Result<(), String> { @@ -263,7 +263,7 @@ impl FileTransferActivity { /// Send many entries to remote fn filetransfer_send_many( &mut self, - entries: &[Entry], + entries: &[File], curr_remote_path: &Path, ) -> Result<(), String> { // Reset states @@ -289,15 +289,12 @@ impl FileTransferActivity { fn filetransfer_send_recurse( &mut self, - entry: &Entry, + entry: &File, curr_remote_path: &Path, dst_name: Option, ) -> Result<(), String> { // Write popup - let file_name: String = match entry { - Entry::Directory(dir) => dir.name.clone(), - Entry::File(file) => file.name.clone(), - }; + let file_name = entry.name(); // Get remote path let mut remote_path: PathBuf = PathBuf::from(curr_remote_path); let remote_file_name: PathBuf = match dst_name { @@ -306,107 +303,104 @@ impl FileTransferActivity { }; remote_path.push(remote_file_name); // Match entry - let result: Result<(), String> = match entry { - Entry::File(file) => { - match self.filetransfer_send_one(file, remote_path.as_path(), file_name) { - Err(err) => { - // If transfer was abrupted or there was an IO error on remote, remove file - if matches!( - err, - TransferErrorReason::Abrupted | TransferErrorReason::RemoteIoError(_) - ) { - // Stat file on remote and remove it if exists - match self.client.stat(remote_path.as_path()) { - Err(err) => self.log( - LogLevel::Error, - format!( - "Could not remove created file {}: {}", - remote_path.display(), - err - ), + let result: Result<(), String> = if entry.is_dir() { + // Create directory on remote first + match self + .client + .create_dir(remote_path.as_path(), UnixPex::from(0o755)) + { + Ok(_) => { + self.log( + LogLevel::Info, + format!("Created directory \"{}\"", remote_path.display()), + ); + } + Err(err) if err.kind == RemoteErrorType::DirectoryAlreadyExists => { + self.log( + LogLevel::Info, + format!( + "Directory \"{}\" already exists on remote", + remote_path.display() + ), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Failed to create directory \"{}\": {}", + remote_path.display(), + err + ), + ); + return Err(err.to_string()); + } + } + // Get files in dir + match self.host.scan_dir(entry.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 + if let Err(err) = + self.filetransfer_send_recurse(entry, remote_path.as_path(), None) + { + return Err(err); + } + } + Ok(()) + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Could not scan directory \"{}\": {}", + entry.path().display(), + err + ), + ); + Err(err.to_string()) + } + } + } else { + match self.filetransfer_send_one(entry, remote_path.as_path(), file_name) { + Err(err) => { + // If transfer was abrupted or there was an IO error on remote, remove file + if matches!( + err, + TransferErrorReason::Abrupted | TransferErrorReason::RemoteIoError(_) + ) { + // Stat file on remote and remove it if exists + match self.client.stat(remote_path.as_path()) { + Err(err) => self.log( + LogLevel::Error, + format!( + "Could not remove created file {}: {}", + remote_path.display(), + err ), - Ok(entry) => { - if let Err(err) = self.client.remove_file(entry.path()) { - self.log( - LogLevel::Error, - format!( - "Could not remove created file {}: {}", - remote_path.display(), - err - ), - ); - } + ), + Ok(entry) => { + if let Err(err) = self.client.remove_file(entry.path()) { + self.log( + LogLevel::Error, + format!( + "Could not remove created file {}: {}", + remote_path.display(), + err + ), + ); } } } - Err(err.to_string()) - } - Ok(_) => Ok(()), - } - } - Entry::Directory(dir) => { - // Create directory on remote first - match self - .client - .create_dir(remote_path.as_path(), UnixPex::from(0o755)) - { - Ok(_) => { - self.log( - LogLevel::Info, - format!("Created directory \"{}\"", remote_path.display()), - ); - } - Err(err) if err.kind == RemoteErrorType::DirectoryAlreadyExists => { - self.log( - LogLevel::Info, - format!( - "Directory \"{}\" already exists on remote", - remote_path.display() - ), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Failed to create directory \"{}\": {}", - remote_path.display(), - err - ), - ); - return Err(err.to_string()); - } - } - // Get files in dir - match self.host.scan_dir(dir.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 - if let Err(err) = - self.filetransfer_send_recurse(entry, remote_path.as_path(), None) - { - return Err(err); - } - } - Ok(()) - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not scan directory \"{}\": {}", - dir.path.display(), - err - ), - ); - Err(err.to_string()) } + Err(err.to_string()) } + Ok(_) => Ok(()), } }; // Scan dir on remote @@ -458,7 +452,7 @@ impl FileTransferActivity { remote: &Path, file_name: String, mut reader: StdFile, - mut writer: Box, + mut writer: WriteStream, ) -> Result<(), TransferErrorReason> { // Write file let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize; @@ -632,7 +626,7 @@ impl FileTransferActivity { /// If entry is a directory, this applies to directory only fn filetransfer_recv_any( &mut self, - entry: &Entry, + entry: &File, local_path: &Path, dst_name: Option, ) -> Result<(), String> { @@ -660,7 +654,7 @@ impl FileTransferActivity { // Mount progress bar self.mount_progress_bar(format!("Downloading {}…", entry.path.display())); // Receive - let result = self.filetransfer_recv_one(local_path, entry, entry.name.clone()); + let result = self.filetransfer_recv_one(local_path, entry, entry.name()); // Umount progress bar self.umount_progress_bar(); // Return result @@ -670,7 +664,7 @@ impl FileTransferActivity { /// Send many entries to remote fn filetransfer_recv_many( &mut self, - entries: &[Entry], + entries: &[File], curr_remote_path: &Path, ) -> Result<(), String> { // Reset states @@ -696,142 +690,132 @@ impl FileTransferActivity { fn filetransfer_recv_recurse( &mut self, - entry: &Entry, + entry: &File, local_path: &Path, dst_name: Option, ) -> Result<(), String> { // Write popup - let file_name: String = match entry { - Entry::Directory(dir) => dir.name.clone(), - Entry::File(file) => file.name.clone(), - }; + let file_name = entry.name(); // Match entry - let result: Result<(), String> = match entry { - Entry::File(file) => { - // Get local file - let mut local_file_path: PathBuf = PathBuf::from(local_path); - let local_file_name: String = match dst_name { - Some(n) => n, - None => file.name.clone(), - }; - local_file_path.push(local_file_name.as_str()); - // Download file - if let Err(err) = - self.filetransfer_recv_one(local_file_path.as_path(), file, file_name) - { - // If transfer was abrupted or there was an IO error on remote, remove file - if matches!( - err, - TransferErrorReason::Abrupted | TransferErrorReason::LocalIoError(_) - ) { - // Stat file - match self.host.stat(local_file_path.as_path()) { - Err(err) => self.log( + let result: Result<(), String> = if entry.is_dir() { + // Get dir name + let mut local_dir_path: PathBuf = PathBuf::from(local_path); + match dst_name { + Some(name) => local_dir_path.push(name), + None => local_dir_path.push(entry.name()), + } + // Create directory on local + match self.host.mkdir_ex(local_dir_path.as_path(), true) { + Ok(_) => { + // Apply file mode to directory + #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))] + if let Some(mode) = entry.metadata().mode { + if let Err(err) = self.host.chmod(local_dir_path.as_path(), mode) { + self.log( LogLevel::Error, format!( - "Could not remove created file {}: {}", - local_file_path.display(), + "Could not apply file mode {:o} to \"{}\": {}", + u32::from(mode), + local_dir_path.display(), err ), - ), - Ok(entry) => { - if let Err(err) = self.host.remove(&entry) { - self.log( - LogLevel::Error, - format!( - "Could not remove created file {}: {}", - local_file_path.display(), - err - ), - ); - } - } + ); } } + self.log( + LogLevel::Info, + format!("Created directory \"{}\"", local_dir_path.display()), + ); + // Get files in dir + match self.client.list_dir(entry.path()) { + Ok(entries) => { + // Iterate over files + for entry in entries.iter() { + // If transfer has been aborted; break + if self.transfer.aborted() { + break; + } + // Receive entry; name is always None after first call + // Local path becomes local_dir_path + if let Err(err) = self.filetransfer_recv_recurse( + entry, + local_dir_path.as_path(), + None, + ) { + return Err(err); + } + } + Ok(()) + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Could not scan directory \"{}\": {}", + entry.path().display(), + err + ), + ); + Err(err.to_string()) + } + } + } + Err(err) => { + self.log( + LogLevel::Error, + format!( + "Failed to create directory \"{}\": {}", + local_dir_path.display(), + err + ), + ); Err(err.to_string()) - } else { - Ok(()) } } - Entry::Directory(dir) => { - // Get dir name - let mut local_dir_path: PathBuf = PathBuf::from(local_path); - match dst_name { - Some(name) => local_dir_path.push(name), - None => local_dir_path.push(dir.name.as_str()), - } - // Create directory on local - match self.host.mkdir_ex(local_dir_path.as_path(), true) { - Ok(_) => { - // Apply file mode to directory - #[cfg(any( - target_family = "unix", - target_os = "macos", - target_os = "linux" - ))] - if let Some(mode) = dir.metadata.mode { - if let Err(err) = self.host.chmod(local_dir_path.as_path(), mode) { + } else { + // Get local file + let mut local_file_path: PathBuf = PathBuf::from(local_path); + let local_file_name: String = match dst_name { + Some(n) => n, + None => entry.name(), + }; + local_file_path.push(local_file_name.as_str()); + // Download file + if let Err(err) = + self.filetransfer_recv_one(local_file_path.as_path(), entry, file_name) + { + // If transfer was abrupted or there was an IO error on remote, remove file + if matches!( + err, + TransferErrorReason::Abrupted | TransferErrorReason::LocalIoError(_) + ) { + // Stat file + match self.host.stat(local_file_path.as_path()) { + Err(err) => self.log( + LogLevel::Error, + format!( + "Could not remove created file {}: {}", + local_file_path.display(), + err + ), + ), + Ok(entry) => { + if let Err(err) = self.host.remove(&entry) { self.log( LogLevel::Error, format!( - "Could not apply file mode {:o} to \"{}\": {}", - u32::from(mode), - local_dir_path.display(), + "Could not remove created file {}: {}", + local_file_path.display(), err ), ); } } - self.log( - LogLevel::Info, - format!("Created directory \"{}\"", local_dir_path.display()), - ); - // Get files in dir - match self.client.list_dir(dir.path.as_path()) { - Ok(entries) => { - // Iterate over files - for entry in entries.iter() { - // If transfer has been aborted; break - if self.transfer.aborted() { - break; - } - // Receive entry; name is always None after first call - // Local path becomes local_dir_path - if let Err(err) = self.filetransfer_recv_recurse( - entry, - local_dir_path.as_path(), - None, - ) { - return Err(err); - } - } - Ok(()) - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Could not scan directory \"{}\": {}", - dir.path.display(), - err - ), - ); - Err(err.to_string()) - } - } - } - Err(err) => { - self.log( - LogLevel::Error, - format!( - "Failed to create directory \"{}\": {}", - local_dir_path.display(), - err - ), - ); - Err(err.to_string()) } } + Err(err.to_string()) + } else { + Ok(()) } }; // Reload directory on local @@ -872,13 +856,13 @@ impl FileTransferActivity { } } - /// Receive an `Entry` from remote using stream + /// Receive an `File` from remote using stream fn filetransfer_recv_one_with_stream( &mut self, local: &Path, remote: &File, file_name: String, - mut reader: Box, + mut reader: ReadStream, mut writer: StdFile, ) -> Result<(), TransferErrorReason> { let mut total_bytes_written: usize = 0; @@ -979,7 +963,7 @@ impl FileTransferActivity { Ok(()) } - /// Receive an `Entry` from remote without using stream + /// Receive an `File` from remote without using stream fn filetransfer_recv_one_wno_stream( &mut self, local: &Path, @@ -1098,7 +1082,7 @@ impl FileTransferActivity { let tmpfile: PathBuf = match self.cache.as_ref() { Some(cache) => { let mut p: PathBuf = cache.path().to_path_buf(); - p.push(file.name.as_str()); + p.push(file.name()); p } None => { @@ -1111,7 +1095,7 @@ impl FileTransferActivity { match self.filetransfer_recv( TransferPayload::File(file.clone()), tmpfile.as_path(), - Some(file.name.clone()), + Some(file.name()), ) { Err(err) => Err(format!( "Could not download {} to temporary file: {}", @@ -1125,48 +1109,54 @@ impl FileTransferActivity { // -- transfer sizes /// Get total size of transfer for localhost - fn get_total_transfer_size_local(&mut self, entry: &Entry) -> usize { - match entry { - Entry::File(file) => file.metadata.size as usize, - Entry::Directory(dir) => { - // List dir - match self.host.scan_dir(dir.path.as_path()) { - Ok(files) => files - .iter() - .map(|x| self.get_total_transfer_size_local(x)) - .sum(), - Err(err) => { - self.log( - LogLevel::Error, - format!("Could not list directory {}: {}", dir.path.display(), err), - ); - 0 - } + fn get_total_transfer_size_local(&mut self, entry: &File) -> usize { + if entry.is_dir() { + // List dir + match self.host.scan_dir(entry.path()) { + Ok(files) => files + .iter() + .map(|x| self.get_total_transfer_size_local(x)) + .sum(), + Err(err) => { + self.log( + LogLevel::Error, + format!( + "Could not list directory {}: {}", + entry.path().display(), + err + ), + ); + 0 } } + } else { + entry.metadata.size as usize } } /// Get total size of transfer for remote host - fn get_total_transfer_size_remote(&mut self, entry: &Entry) -> usize { - match entry { - Entry::File(file) => file.metadata.size as usize, - Entry::Directory(dir) => { - // List directory - match self.client.list_dir(dir.path.as_path()) { - Ok(files) => files - .iter() - .map(|x| self.get_total_transfer_size_remote(x)) - .sum(), - Err(err) => { - self.log( - LogLevel::Error, - format!("Could not list directory {}: {}", dir.path.display(), err), - ); - 0 - } + fn get_total_transfer_size_remote(&mut self, entry: &File) -> usize { + if entry.is_dir() { + // List directory + match self.client.list_dir(entry.path()) { + Ok(files) => files + .iter() + .map(|x| self.get_total_transfer_size_remote(x)) + .sum(), + Err(err) => { + self.log( + LogLevel::Error, + format!( + "Could not list directory {}: {}", + entry.path().display(), + err + ), + ); + 0 } } + } else { + entry.metadata.size as usize } } diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 0924447..0da7355 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -27,12 +27,12 @@ */ // locals use super::{ - actions::SelectedEntry, + actions::SelectedFile, browser::{FileExplorerTab, FoundExplorerTab}, ExitReason, FileTransferActivity, Id, Msg, TransferMsg, TransferOpts, UiMsg, }; // externals -use remotefs::fs::Entry; +use remotefs::fs::File; use tuirealm::{ props::{AttrValue, Attribute}, State, StateValue, Update, @@ -121,7 +121,7 @@ impl FileTransferActivity { } } TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Local => { - if let SelectedEntry::One(entry) = self.get_local_selected_entries() { + if let SelectedFile::One(entry) = self.get_local_selected_entries() { self.action_submit_local(entry); // Update file list if sync if self.browser.sync_browsing && self.browser.found().is_none() { @@ -131,7 +131,7 @@ impl FileTransferActivity { } } TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Remote => { - if let SelectedEntry::One(entry) = self.get_remote_selected_entries() { + if let SelectedFile::One(entry) = self.get_remote_selected_entries() { self.action_submit_remote(entry); // Update file list if sync if self.browser.sync_browsing && self.browser.found().is_none() { @@ -296,7 +296,7 @@ impl FileTransferActivity { // Mount wait self.mount_blocking_wait(format!(r#"Searching for "{}"…"#, search).as_str()); // Find - let res: Result, String> = match self.browser.tab() { + let res: Result, String> = match self.browser.tab() { FileExplorerTab::Local => self.action_local_find(search.clone()), FileExplorerTab::Remote => self.action_remote_find(search.clone()), _ => panic!("Trying to search for files, while already in a find result"), @@ -449,17 +449,17 @@ impl FileTransferActivity { UiMsg::ShowDisconnectPopup => self.mount_disconnect(), UiMsg::ShowExecPopup => self.mount_exec(), UiMsg::ShowFileInfoPopup if self.browser.tab() == FileExplorerTab::Local => { - if let SelectedEntry::One(file) = self.get_local_selected_entries() { + if let SelectedFile::One(file) = self.get_local_selected_entries() { self.mount_file_info(&file); } } UiMsg::ShowFileInfoPopup if self.browser.tab() == FileExplorerTab::Remote => { - if let SelectedEntry::One(file) = self.get_remote_selected_entries() { + if let SelectedFile::One(file) = self.get_remote_selected_entries() { self.mount_file_info(&file); } } UiMsg::ShowFileInfoPopup => { - if let SelectedEntry::One(file) = self.get_found_selected_entries() { + if let SelectedFile::One(file) = self.get_found_selected_entries() { self.mount_file_info(&file); } } diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index a3ae291..64de25b 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -33,7 +33,7 @@ use super::{ use crate::explorer::FileSorting; use crate::utils::ui::draw_area_in; // Ext -use remotefs::fs::Entry; +use remotefs::fs::File; use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::tui::layout::{Constraint, Direction, Layout}; use tuirealm::tui::widgets::Clear; @@ -719,7 +719,7 @@ impl FileTransferActivity { assert!(self.app.active(&Id::ReplacePopup).is_ok()); } - pub(super) fn mount_radio_replace_many(&mut self, files: &[&str]) { + pub(super) fn mount_radio_replace_many(&mut self, files: &[String]) { let warn_color = self.theme().misc_warn_dialog; assert!(self .app @@ -750,7 +750,7 @@ impl FileTransferActivity { let _ = self.app.umount(&Id::ReplacingFilesListPopup); // NOTE: replace anyway } - pub(super) fn mount_file_info(&mut self, file: &Entry) { + pub(super) fn mount_file_info(&mut self, file: &File) { assert!(self .app .remount( diff --git a/src/utils/test_helpers.rs b/src/utils/test_helpers.rs index 52ca22c..091206c 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 remotefs::fs::{Directory, Entry, File, Metadata}; +use remotefs::fs::{File, FileType, Metadata}; // ext use std::fs::File as StdFile; use std::io::Write; @@ -37,14 +37,7 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) { let tmpfile = create_sample_file(); ( File { - name: tmpfile - .path() - .file_name() - .unwrap() - .to_string_lossy() - .to_string(), path: tmpfile.path().to_path_buf(), - extension: None, metadata: Metadata::default(), }, tmpfile, @@ -85,20 +78,15 @@ pub fn make_dir_at(dir: &Path, dirname: &str) -> std::io::Result<()> { std::fs::create_dir(p.as_path()) } -/// Create a Entry at specified path -pub fn make_fsentry>(path: P, is_dir: bool) -> Entry { +/// Create a File at specified path +pub fn make_fsentry>(path: P, is_dir: bool) -> File { let path: PathBuf = path.as_ref().to_path_buf(); - match is_dir { - true => Entry::Directory(Directory { - name: path.file_name().unwrap().to_string_lossy().to_string(), - path, - metadata: Metadata::default(), - }), - false => Entry::File(File { - name: path.file_name().unwrap().to_string_lossy().to_string(), - path, - extension: None, - metadata: Metadata::default(), + File { + path, + metadata: Metadata::default().file_type(if is_dir { + FileType::Directory + } else { + FileType::File }), } } @@ -127,15 +115,13 @@ mod test { fn test_utils_test_helpers_make_fsentry() { assert_eq!( make_fsentry(PathBuf::from("/tmp/omar.txt"), false) - .unwrap_file() - .name + .name() .as_str(), "omar.txt" ); assert_eq!( make_fsentry(PathBuf::from("/tmp/cards"), true) - .unwrap_dir() - .name + .name() .as_str(), "cards" );