remotefs 0.2.0

This commit is contained in:
veeso
2022-01-04 12:48:36 +01:00
committed by Christian Visintin
parent edd0842273
commit ec4daf8e25
28 changed files with 1013 additions and 1095 deletions

View File

@@ -70,7 +70,7 @@ Released on FIXME:
- Dependencies: - Dependencies:
- Updated `tui-realm` to `1.3.0` - Updated `tui-realm` to `1.3.0`
- Updated `tui-realm-stdlib` to `1.1.4` - 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) - Removed `crossterm` (since bridged by tui-realm)
## 0.7.0 ## 0.7.0

57
Cargo.lock generated
View File

@@ -1676,22 +1676,59 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]] [[package]]
name = "remotefs" name = "remotefs"
version = "0.1.1" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"chrono", "chrono",
"lazy_static", "lazy_static",
"log", "log",
"path-slash", "path-slash",
"regex", "regex",
"rust-s3", "remotefs",
"ssh2", "ssh2",
"ssh2-config", "ssh2-config",
"suppaftp",
"thiserror",
"users", "users",
"wildmatch",
] ]
[[package]] [[package]]
@@ -2095,10 +2132,11 @@ dependencies = [
[[package]] [[package]]
name = "ssh2-config" name = "ssh2-config"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e64d0ea4897c9415c34011a4cdf21a0e0168c200595f1f543be1ca807942d8" checksum = "0e55cdf8a42b24c57788bc8b85e8d6e7f6e0614b5316322b2e7f455f9d93230e"
dependencies = [ dependencies = [
"dirs 4.0.0",
"thiserror", "thiserror",
"wildmatch", "wildmatch",
] ]
@@ -2236,6 +2274,9 @@ dependencies = [
"rand 0.8.4", "rand 0.8.4",
"regex", "regex",
"remotefs", "remotefs",
"remotefs-aws-s3",
"remotefs-ftp",
"remotefs-ssh",
"rpassword", "rpassword",
"self_update", "self_update",
"serde", "serde",

View File

@@ -48,7 +48,10 @@ notify-rust = { version = "4.5.3", default-features = false, features = [ "d" ]
open = "2.0.1" open = "2.0.1"
rand = "0.8.4" rand = "0.8.4"
regex = "1.5.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" rpassword = "5.0.1"
self_update = { version = "0.27.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] } self_update = { version = "0.27.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
serde = { version = "^1.0.0", features = [ "derive" ] } serde = { version = "^1.0.0", features = [ "derive" ] }
@@ -74,3 +77,9 @@ with-keyring = [ "keyring" ]
[target."cfg(target_family = \"unix\")"] [target."cfg(target_family = \"unix\")"]
[target."cfg(target_family = \"unix\")".dependencies] [target."cfg(target_family = \"unix\")".dependencies]
users = "0.11.0" users = "0.11.0"
[profile.release]
incremental = true
[profile.dev]
incremental = true

View File

@@ -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: 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. - **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. - **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. - **utils**: contains the utilities used by pretty much all the project.

View File

@@ -31,13 +31,14 @@ use crate::utils::path::diff_paths;
// Ext // Ext
use bytesize::ByteSize; use bytesize::ByteSize;
use regex::Regex; use regex::Regex;
use remotefs::Entry; use remotefs::File;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::UNIX_EPOCH;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use users::{get_group_by_gid, get_user_by_uid}; use users::{get_group_by_gid, get_user_by_uid};
// Types // Types
// FmtCallback: Formatter, fsentry: &Entry, cur_str, prefix, length, extra // FmtCallback: Formatter, fsentry: &File, cur_str, prefix, length, extra
type FmtCallback = fn(&Formatter, &Entry, &str, &str, Option<&usize>, Option<&String>) -> String; type FmtCallback = fn(&Formatter, &File, &str, &str, Option<&usize>, Option<&String>) -> String;
// Keys // Keys
const FMT_KEY_ATIME: &str = "ATIME"; 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(); 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 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. /// a chain of function is made using the Formatters method.
/// This method provides an extremely fast way to format fs entries /// This method provides an extremely fast way to format fs entries
@@ -99,7 +100,7 @@ impl CallChainBlock {
} }
/// Call next callback in the CallChain /// 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 // Call func
let new_str: String = (self.func)( let new_str: String = (self.func)(
fmt, fmt,
@@ -161,7 +162,7 @@ impl Formatter {
} }
/// Format fsentry /// Format fsentry
pub fn fmt(&self, fsentry: &Entry) -> String { pub fn fmt(&self, fsentry: &File) -> String {
// Execute callchain blocks // Execute callchain blocks
self.call_chain.next(self, fsentry, "") self.call_chain.next(self, fsentry, "")
} }
@@ -171,7 +172,7 @@ impl Formatter {
/// Format last access time /// Format last access time
fn fmt_atime( fn fmt_atime(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -179,7 +180,7 @@ impl Formatter {
) -> String { ) -> String {
// Get date (use extra args as format or default "%b %d %Y %H:%M") // Get date (use extra args as format or default "%b %d %Y %H:%M")
let datetime: String = fmt_time( let datetime: String = fmt_time(
fsentry.metadata().atime, fsentry.metadata().accessed.unwrap_or(UNIX_EPOCH),
match fmt_extra { match fmt_extra {
Some(fmt) => fmt.as_ref(), Some(fmt) => fmt.as_ref(),
None => "%b %d %Y %H:%M", None => "%b %d %Y %H:%M",
@@ -198,7 +199,7 @@ impl Formatter {
/// Format creation time /// Format creation time
fn fmt_ctime( fn fmt_ctime(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -206,7 +207,7 @@ impl Formatter {
) -> String { ) -> String {
// Get date // Get date
let datetime: String = fmt_time( let datetime: String = fmt_time(
fsentry.metadata().ctime, fsentry.metadata().created.unwrap_or(UNIX_EPOCH),
match fmt_extra { match fmt_extra {
Some(fmt) => fmt.as_ref(), Some(fmt) => fmt.as_ref(),
None => "%b %d %Y %H:%M", None => "%b %d %Y %H:%M",
@@ -225,7 +226,7 @@ impl Formatter {
/// Format owner group /// Format owner group
fn fmt_group( fn fmt_group(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -258,7 +259,7 @@ impl Formatter {
/// Format last change time /// Format last change time
fn fmt_mtime( fn fmt_mtime(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -266,7 +267,7 @@ impl Formatter {
) -> String { ) -> String {
// Get date // Get date
let datetime: String = fmt_time( let datetime: String = fmt_time(
fsentry.metadata().mtime, fsentry.metadata().modified.unwrap_or(UNIX_EPOCH),
match fmt_extra { match fmt_extra {
Some(fmt) => fmt.as_ref(), Some(fmt) => fmt.as_ref(),
None => "%b %d %Y %H:%M", None => "%b %d %Y %H:%M",
@@ -285,7 +286,7 @@ impl Formatter {
/// Format file name /// Format file name
fn fmt_name( fn fmt_name(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -296,14 +297,14 @@ impl Formatter {
Some(l) => *l, Some(l) => *l,
None => 24, None => 24,
}; };
let name: &str = fsentry.name(); let name = fsentry.name();
let last_idx: usize = match fsentry.is_dir() { let last_idx: usize = match fsentry.is_dir() {
// NOTE: For directories is l - 2, since we push '/' to name // NOTE: For directories is l - 2, since we push '/' to name
true => file_len - 2, true => file_len - 2,
false => file_len - 1, false => file_len - 1,
}; };
let mut name: String = match name.len() >= file_len { let mut name: String = match name.len() >= file_len {
false => name.to_string(), false => name,
true => format!("{}", &name[0..last_idx]), true => format!("{}", &name[0..last_idx]),
}; };
if fsentry.is_dir() { if fsentry.is_dir() {
@@ -316,7 +317,7 @@ impl Formatter {
/// Format path /// Format path
fn fmt_path( fn fmt_path(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -341,7 +342,7 @@ impl Formatter {
/// Format file permissions /// Format file permissions
fn fmt_pex( fn fmt_pex(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
_fmt_len: Option<&usize>, _fmt_len: Option<&usize>,
@@ -376,7 +377,7 @@ impl Formatter {
/// Format file size /// Format file size
fn fmt_size( fn fmt_size(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
_fmt_len: Option<&usize>, _fmt_len: Option<&usize>,
@@ -387,6 +388,17 @@ impl Formatter {
let size: ByteSize = ByteSize(fsentry.metadata().size); let size: ByteSize = ByteSize(fsentry.metadata().size);
// Add to cur str, prefix and the key value // Add to cur str, prefix and the key value
format!("{}{}{:10}", cur_str, prefix, size.to_string()) 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 { } else {
// Add to cur str, prefix and the key value // Add to cur str, prefix and the key value
format!("{}{} ", cur_str, prefix) format!("{}{} ", cur_str, prefix)
@@ -396,7 +408,7 @@ impl Formatter {
/// Format file symlink (if any) /// Format file symlink (if any)
fn fmt_symlink( fn fmt_symlink(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
fmt_len: Option<&usize>, fmt_len: Option<&usize>,
@@ -423,7 +435,7 @@ impl Formatter {
/// Format owner user /// Format owner user
fn fmt_user( fn fmt_user(
&self, &self,
fsentry: &Entry, fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
_fmt_len: Option<&usize>, _fmt_len: Option<&usize>,
@@ -451,7 +463,7 @@ impl Formatter {
/// It does nothing, just returns cur_str /// It does nothing, just returns cur_str
fn fmt_fallback( fn fmt_fallback(
&self, &self,
_fsentry: &Entry, _fsentry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
_fmt_len: Option<&usize>, _fmt_len: Option<&usize>,
@@ -536,7 +548,7 @@ mod tests {
use super::*; use super::*;
use pretty_assertions::assert_eq; 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::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
@@ -546,21 +558,20 @@ mod tests {
let dummy_formatter: Formatter = Formatter::new(""); let dummy_formatter: Formatter = Formatter::new("");
// Make a dummy entry // Make a dummy entry
let t: SystemTime = SystemTime::now(); let t: SystemTime = SystemTime::now();
let dummy_entry: Entry = Entry::File(File { let dummy_entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
symlink: None, symlink: None,
uid: Some(0), uid: Some(0),
gid: Some(0), gid: Some(0),
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
let prefix: String = String::from("h"); let prefix: String = String::from("h");
let mut callchain: CallChainBlock = CallChainBlock::new(dummy_fmt, prefix, None, None); let mut callchain: CallChainBlock = CallChainBlock::new(dummy_fmt, prefix, None, None);
assert!(callchain.next_block.is_none()); assert!(callchain.next_block.is_none());
@@ -588,21 +599,20 @@ mod tests {
let formatter: Formatter = Formatter::default(); let formatter: Formatter = Formatter::default();
// Experiments :D // Experiments :D
let t: SystemTime = SystemTime::now(); let t: SystemTime = SystemTime::now();
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
symlink: None, symlink: None,
uid: Some(0), uid: Some(0),
gid: Some(0), gid: Some(0),
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
formatter.fmt(&entry), formatter.fmt(&entry),
@@ -620,21 +630,20 @@ mod tests {
) )
); );
// Elide name // Elide name
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("piroparoporoperoperupupu.txt"), path: PathBuf::from("/piroparoporoperoperupupu.txt"),
path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
symlink: None, symlink: None,
uid: Some(0), uid: Some(0),
gid: Some(0), gid: Some(0),
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
formatter.fmt(&entry), formatter.fmt(&entry),
@@ -652,21 +661,20 @@ mod tests {
) )
); );
// No pex // No pex
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
symlink: None, symlink: None,
uid: Some(0), uid: Some(0),
gid: Some(0), gid: Some(0),
mode: None, mode: None,
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
formatter.fmt(&entry), formatter.fmt(&entry),
@@ -684,21 +692,20 @@ mod tests {
) )
); );
// No user // No user
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
symlink: None, symlink: None,
uid: None, uid: None,
gid: Some(0), gid: Some(0),
mode: None, mode: None,
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
formatter.fmt(&entry), formatter.fmt(&entry),
@@ -723,20 +730,20 @@ mod tests {
let formatter: Formatter = Formatter::default(); let formatter: Formatter = Formatter::default();
// Experiments :D // Experiments :D
let t: SystemTime = SystemTime::now(); let t: SystemTime = SystemTime::now();
let entry: Entry = Entry::Directory(Directory { let entry = File {
name: String::from("projects"),
path: PathBuf::from("/home/cvisintin/projects"), path: PathBuf::from("/home/cvisintin/projects"),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::Directory,
size: 4096, size: 4096,
symlink: None, symlink: None,
uid: Some(0), uid: Some(0),
gid: Some(0), gid: Some(0),
mode: Some(UnixPex::from(0o755)), mode: Some(UnixPex::from(0o755)),
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
formatter.fmt(&entry), formatter.fmt(&entry),
@@ -754,20 +761,20 @@ mod tests {
) )
); );
// No pex, no user // No pex, no user
let entry: Entry = Entry::Directory(Directory { let entry = File {
name: String::from("projects"),
path: PathBuf::from("/home/cvisintin/projects"), path: PathBuf::from("/home/cvisintin/projects"),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::Directory,
size: 4096, size: 4096,
symlink: None, symlink: None,
uid: None, uid: None,
gid: Some(0), gid: Some(0),
mode: None, mode: None,
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
formatter.fmt(&entry), 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}"); 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) // Directory (with symlink)
let t: SystemTime = SystemTime::now(); let t: SystemTime = SystemTime::now();
let entry: Entry = Entry::Directory(Directory { let entry = File {
name: String::from("projects"), path: PathBuf::from("/home/cvisintin/projects"),
path: PathBuf::from("/home/cvisintin/project"),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::Symlink,
size: 4096, size: 4096,
symlink: Some(PathBuf::from("project.info")), symlink: Some(PathBuf::from("project.info")),
uid: None, uid: None,
gid: None, gid: None,
mode: Some(UnixPex::from(0o755)), mode: Some(UnixPex::from(0o755)),
}, },
}); };
assert_eq!(formatter.fmt(&entry), format!( 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"), 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 // Directory without symlink
let entry: Entry = Entry::Directory(Directory { let entry = File {
name: String::from("projects"), path: PathBuf::from("/home/cvisintin/projects"),
path: PathBuf::from("/home/cvisintin/project"),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::Directory,
size: 4096, size: 4096,
symlink: None, symlink: None,
uid: None, uid: None,
gid: None, gid: None,
mode: Some(UnixPex::from(0o755)), mode: Some(UnixPex::from(0o755)),
}, },
}); };
assert_eq!(formatter.fmt(&entry), format!( assert_eq!(formatter.fmt(&entry), format!(
"projects/ 0 0 drwxr-xr-x {} {} {}", "projects/ 0 0 drwxr-xr-x {} {} {}",
fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"),
@@ -834,43 +841,41 @@ mod tests {
fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"),
)); ));
// File with symlink // File with symlink
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::Symlink,
size: 8192, size: 8192,
symlink: Some(PathBuf::from("project.info")), symlink: Some(PathBuf::from("project.info")),
uid: None, uid: None,
gid: None, gid: None,
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
assert_eq!(formatter.fmt(&entry), format!( 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"), 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 // File without symlink
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
symlink: None, symlink: None,
uid: None, uid: None,
gid: None, gid: None,
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
assert_eq!(formatter.fmt(&entry), format!( assert_eq!(formatter.fmt(&entry), format!(
"bar.txt 0 0 -rw-r--r-- 8.2 KB {} {} {}", "bar.txt 0 0 -rw-r--r-- 8.2 KB {} {} {}",
fmt_time(t, "%a %b %d %Y %H:%M"), fmt_time(t, "%a %b %d %Y %H:%M"),
@@ -883,21 +888,20 @@ mod tests {
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
fn should_fmt_path() { fn should_fmt_path() {
let t: SystemTime = SystemTime::now(); let t: SystemTime = SystemTime::now();
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/tmp/a/b/c/bar.txt"), path: PathBuf::from("/tmp/a/b/c/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: FileType::Symlink,
size: 8192, size: 8192,
symlink: Some(PathBuf::from("project.info")), symlink: Some(PathBuf::from("project.info")),
uid: None, uid: None,
gid: None, gid: None,
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
let formatter: Formatter = Formatter::new("File path: {PATH}"); let formatter: Formatter = Formatter::new("File path: {PATH}");
assert_eq!( assert_eq!(
formatter.fmt(&entry).as_str(), formatter.fmt(&entry).as_str(),
@@ -915,7 +919,7 @@ mod tests {
/// Dummy formatter, just yelds an 'A' at the end of the current string /// Dummy formatter, just yelds an 'A' at the end of the current string
fn dummy_fmt( fn dummy_fmt(
_fmt: &Formatter, _fmt: &Formatter,
_entry: &Entry, _entry: &File,
cur_str: &str, cur_str: &str,
prefix: &str, prefix: &str,
_fmt_len: Option<&usize>, _fmt_len: Option<&usize>,

View File

@@ -31,7 +31,7 @@ mod formatter;
// Locals // Locals
use formatter::Formatter; use formatter::Formatter;
// Ext // Ext
use remotefs::fs::Entry; use remotefs::fs::File;
use std::cmp::Reverse; use std::cmp::Reverse;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -71,8 +71,8 @@ pub struct FileExplorer {
pub(crate) file_sorting: FileSorting, // File sorting criteria pub(crate) file_sorting: FileSorting, // File sorting criteria
pub(crate) group_dirs: Option<GroupDirs>, // If Some, defines how to group directories pub(crate) group_dirs: Option<GroupDirs>, // If Some, defines how to group directories
pub(crate) opts: ExplorerOpts, // Explorer options pub(crate) opts: ExplorerOpts, // Explorer options
pub(crate) fmt: Formatter, // Entry formatter pub(crate) fmt: Formatter, // File formatter
files: Vec<Entry>, // Files in directory files: Vec<File>, // Files in directory
} }
impl Default for FileExplorer { impl Default for FileExplorer {
@@ -109,7 +109,7 @@ impl FileExplorer {
/// Set Explorer files /// Set Explorer files
/// This method will also sort entries based on current options /// This method will also sort entries based on current options
/// Once all sorting have been performed, index is moved to first valid entry. /// Once all sorting have been performed, index is moved to first valid entry.
pub fn set_files(&mut self, files: Vec<Entry>) { pub fn set_files(&mut self, files: Vec<File>) {
self.files = files; self.files = files;
// Sort // Sort
self.sort(); self.sort();
@@ -131,7 +131,7 @@ impl FileExplorer {
/// Iterate over files /// Iterate over files
/// Filters are applied based on current options (e.g. hidden files not returned) /// Filters are applied based on current options (e.g. hidden files not returned)
pub fn iter_files(&self) -> impl Iterator<Item = &Entry> + '_ { pub fn iter_files(&self) -> impl Iterator<Item = &File> + '_ {
// Filter // Filter
let opts: ExplorerOpts = self.opts; let opts: ExplorerOpts = self.opts;
Box::new(self.files.iter().filter(move |x| { Box::new(self.files.iter().filter(move |x| {
@@ -146,12 +146,12 @@ impl FileExplorer {
} }
/// Iterate all files; doesn't care about options /// Iterate all files; doesn't care about options
pub fn iter_files_all(&self) -> impl Iterator<Item = &Entry> + '_ { pub fn iter_files_all(&self) -> impl Iterator<Item = &File> + '_ {
Box::new(self.files.iter()) Box::new(self.files.iter())
} }
/// Get file at relative index /// 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 opts: ExplorerOpts = self.opts;
let filtered = self let filtered = self
.files .files
@@ -172,7 +172,7 @@ impl FileExplorer {
// Formatting // Formatting
/// Format a file entry /// Format a file entry
pub fn fmt_file(&self, entry: &Entry) -> String { pub fn fmt_file(&self, entry: &File) -> String {
self.fmt.fmt(entry) self.fmt.fmt(entry)
} }
@@ -222,35 +222,35 @@ impl FileExplorer {
/// Sort explorer files by their name. All names are converted to lowercase /// Sort explorer files by their name. All names are converted to lowercase
fn sort_files_by_name(&mut self) { 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 /// Sort files by mtime; the newest comes first
fn sort_files_by_mtime(&mut self) { fn sort_files_by_mtime(&mut self) {
self.files 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 /// Sort files by creation time; the newest comes first
fn sort_files_by_creation_time(&mut self) { fn sort_files_by_creation_time(&mut self) {
self.files self.files
.sort_by_key(|b: &Entry| Reverse(b.metadata().ctime)); .sort_by_key(|b: &File| Reverse(b.metadata().created));
} }
/// Sort files by size /// Sort files by size
fn sort_files_by_size(&mut self) { fn sort_files_by_size(&mut self) {
self.files 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 /// Sort files; directories come first
fn sort_files_directories_first(&mut self) { 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 /// Sort files; directories come last
fn sort_files_directories_last(&mut self) { 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 /// Enable/disable hidden files
@@ -317,7 +317,7 @@ mod tests {
use crate::utils::fmt::fmt_time; use crate::utils::fmt::fmt_time;
use pretty_assertions::assert_eq; 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::thread::sleep;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@@ -371,8 +371,8 @@ mod tests {
// Create files // Create files
explorer.set_files(vec![ explorer.set_files(vec![
make_fs_entry("README.md", false), make_fs_entry("README.md", false),
make_fs_entry("src/", true), make_fs_entry("src", true),
make_fs_entry(".git/", true), make_fs_entry(".git", true),
make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CONTRIBUTING.md", false),
make_fs_entry("codecov.yml", false), make_fs_entry("codecov.yml", false),
make_fs_entry(".gitignore", false), make_fs_entry(".gitignore", false),
@@ -381,7 +381,7 @@ mod tests {
assert!(explorer.get(100).is_none()); assert!(explorer.get(100).is_none());
//assert_eq!(explorer.count(), 6); //assert_eq!(explorer.count(), 6);
// Verify (files are sorted by name) // 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) // Iter files (all)
assert_eq!(explorer.iter_files_all().count(), 6); assert_eq!(explorer.iter_files_all().count(), 6);
// Iter files (hidden excluded) (.git, .gitignore are hidden) // Iter files (hidden excluded) (.git, .gitignore are hidden)
@@ -398,7 +398,7 @@ mod tests {
// Create files (files are then sorted by name) // Create files (files are then sorted by name)
explorer.set_files(vec![ explorer.set_files(vec![
make_fs_entry("README.md", false), 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("CONTRIBUTING.md", false),
make_fs_entry("CODE_OF_CONDUCT.md", false), make_fs_entry("CODE_OF_CONDUCT.md", false),
make_fs_entry("CHANGELOG.md", false), make_fs_entry("CHANGELOG.md", false),
@@ -410,39 +410,39 @@ mod tests {
explorer.sort_by(FileSorting::Name); explorer.sort_by(FileSorting::Name);
// First entry should be "Cargo.lock" // First entry should be "Cargo.lock"
assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock");
// Last should be "src/" // Last should be "src"
assert_eq!(explorer.files.get(8).unwrap().name(), "src/"); assert_eq!(explorer.files.get(8).unwrap().name(), "src");
} }
#[test] #[test]
fn test_fs_explorer_sort_by_mtime() { fn test_fs_explorer_sort_by_mtime() {
let mut explorer: FileExplorer = FileExplorer::default(); 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 // Wait 1 sec
sleep(Duration::from_secs(1)); 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) // Create files (files are then sorted by name)
explorer.set_files(vec![entry1, entry2]); explorer.set_files(vec![entry1, entry2]);
explorer.sort_by(FileSorting::ModifyTime); explorer.sort_by(FileSorting::ModifyTime);
// First entry should be "CODE_OF_CONDUCT.md" // First entry should be "CODE_OF_CONDUCT.md"
assert_eq!(explorer.files.get(0).unwrap().name(), "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"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md");
} }
#[test] #[test]
fn test_fs_explorer_sort_by_creation_time() { fn test_fs_explorer_sort_by_creation_time() {
let mut explorer: FileExplorer = FileExplorer::default(); 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 // Wait 1 sec
sleep(Duration::from_secs(1)); 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) // Create files (files are then sorted by name)
explorer.set_files(vec![entry1, entry2]); explorer.set_files(vec![entry1, entry2]);
explorer.sort_by(FileSorting::CreationTime); explorer.sort_by(FileSorting::CreationTime);
// First entry should be "CODE_OF_CONDUCT.md" // First entry should be "CODE_OF_CONDUCT.md"
assert_eq!(explorer.files.get(0).unwrap().name(), "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"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md");
} }
@@ -452,12 +452,12 @@ mod tests {
// Create files (files are then sorted by name) // Create files (files are then sorted by name)
explorer.set_files(vec![ explorer.set_files(vec![
make_fs_entry_with_size("README.md", false, 1024), 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), make_fs_entry_with_size("CONTRIBUTING.md", false, 256),
]); ]);
explorer.sort_by(FileSorting::Size); explorer.sort_by(FileSorting::Size);
// Directory has size 4096 // 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(1).unwrap().name(), "README.md");
assert_eq!(explorer.files.get(2).unwrap().name(), "CONTRIBUTING.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) // Create files (files are then sorted by name)
explorer.set_files(vec![ explorer.set_files(vec![
make_fs_entry("README.md", false), make_fs_entry("README.md", false),
make_fs_entry("src/", true), make_fs_entry("src", true),
make_fs_entry("docs/", true), make_fs_entry("docs", true),
make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CONTRIBUTING.md", false),
make_fs_entry("CODE_OF_CONDUCT.md", false), make_fs_entry("CODE_OF_CONDUCT.md", false),
make_fs_entry("CHANGELOG.md", false), make_fs_entry("CHANGELOG.md", false),
@@ -481,8 +481,8 @@ mod tests {
explorer.sort_by(FileSorting::Name); explorer.sort_by(FileSorting::Name);
explorer.group_dirs_by(Some(GroupDirs::First)); explorer.group_dirs_by(Some(GroupDirs::First));
// First entry should be "docs" // First entry should be "docs"
assert_eq!(explorer.files.get(0).unwrap().name(), "docs/"); assert_eq!(explorer.files.get(0).unwrap().name(), "docs");
assert_eq!(explorer.files.get(1).unwrap().name(), "src/"); assert_eq!(explorer.files.get(1).unwrap().name(), "src");
// 3rd is file first for alphabetical order // 3rd is file first for alphabetical order
assert_eq!(explorer.files.get(2).unwrap().name(), "Cargo.lock"); assert_eq!(explorer.files.get(2).unwrap().name(), "Cargo.lock");
// Last should be "README.md" (last file for alphabetical ordening) // Last should be "README.md" (last file for alphabetical ordening)
@@ -495,8 +495,8 @@ mod tests {
// Create files (files are then sorted by name) // Create files (files are then sorted by name)
explorer.set_files(vec![ explorer.set_files(vec![
make_fs_entry("README.md", false), make_fs_entry("README.md", false),
make_fs_entry("src/", true), make_fs_entry("src", true),
make_fs_entry("docs/", true), make_fs_entry("docs", true),
make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CONTRIBUTING.md", false),
make_fs_entry("CODE_OF_CONDUCT.md", false), make_fs_entry("CODE_OF_CONDUCT.md", false),
make_fs_entry("CHANGELOG.md", false), make_fs_entry("CHANGELOG.md", false),
@@ -508,8 +508,8 @@ mod tests {
explorer.sort_by(FileSorting::Name); explorer.sort_by(FileSorting::Name);
explorer.group_dirs_by(Some(GroupDirs::Last)); explorer.group_dirs_by(Some(GroupDirs::Last));
// Last entry should be "src" // Last entry should be "src"
assert_eq!(explorer.files.get(8).unwrap().name(), "docs/"); assert_eq!(explorer.files.get(8).unwrap().name(), "docs");
assert_eq!(explorer.files.get(9).unwrap().name(), "src/"); assert_eq!(explorer.files.get(9).unwrap().name(), "src");
// first is file for alphabetical order // first is file for alphabetical order
assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock");
// Last in files should be "README.md" (last file for alphabetical ordening) // Last in files should be "README.md" (last file for alphabetical ordening)
@@ -521,21 +521,20 @@ mod tests {
let explorer: FileExplorer = FileExplorer::default(); let explorer: FileExplorer = FileExplorer::default();
// Create fs entry // Create fs entry
let t: SystemTime = SystemTime::now(); let t: SystemTime = SystemTime::now();
let entry: Entry = Entry::File(File { let entry = File {
name: String::from("bar.txt"),
path: PathBuf::from("/bar.txt"), path: PathBuf::from("/bar.txt"),
extension: Some(String::from("txt")),
metadata: Metadata { metadata: Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
modified: Some(t),
file_type: FileType::File,
size: 8192, size: 8192,
mtime: t,
symlink: None, symlink: None,
uid: Some(0), uid: Some(0),
gid: Some(0), gid: Some(0),
mode: Some(UnixPex::from(0o644)), mode: Some(UnixPex::from(0o644)),
}, },
}); };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
assert_eq!( assert_eq!(
explorer.fmt_file(&entry), explorer.fmt_file(&entry),
@@ -592,68 +591,60 @@ mod tests {
// Create files (files are then sorted by name) // Create files (files are then sorted by name)
explorer.set_files(vec![ explorer.set_files(vec![
make_fs_entry("CONTRIBUTING.md", false), make_fs_entry("CONTRIBUTING.md", false),
make_fs_entry("docs/", true), make_fs_entry("docs", true),
make_fs_entry("src/", true), make_fs_entry("src", true),
make_fs_entry("README.md", false), make_fs_entry("README.md", false),
]); ]);
explorer.del_entry(0); explorer.del_entry(0);
assert_eq!(explorer.files.len(), 3); assert_eq!(explorer.files.len(), 3);
assert_eq!(explorer.files[0].name(), "docs/"); assert_eq!(explorer.files[0].name(), "docs");
explorer.del_entry(5); explorer.del_entry(5);
assert_eq!(explorer.files.len(), 3); 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 t: SystemTime = SystemTime::now();
let metadata = Metadata { let metadata = Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: if is_dir {
FileType::Directory
} else {
FileType::File
},
symlink: None, symlink: None,
gid: Some(0), gid: Some(0),
uid: Some(0), uid: Some(0),
mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })),
size: 64, size: 64,
}; };
match is_dir { File {
false => Entry::File(File { path: PathBuf::from(name),
name: name.to_string(), metadata,
path: PathBuf::from(name),
extension: None,
metadata,
}),
true => Entry::Directory(Directory {
name: name.to_string(),
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 t: SystemTime = SystemTime::now();
let metadata = Metadata { let metadata = Metadata {
atime: t, accessed: Some(t),
ctime: t, created: Some(t),
mtime: t, modified: Some(t),
file_type: if is_dir {
FileType::Directory
} else {
FileType::File
},
symlink: None, symlink: None,
gid: Some(0), gid: Some(0),
uid: Some(0), uid: Some(0),
mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })), mode: Some(UnixPex::from(if is_dir { 0o755 } else { 0o644 })),
size: size as u64, size: size as u64,
}; };
match is_dir { File {
false => Entry::File(File { path: PathBuf::from(name),
name: name.to_string(), metadata,
path: PathBuf::from(name),
extension: None,
metadata,
}),
true => Entry::Directory(Directory {
name: name.to_string(),
path: PathBuf::from(name),
metadata,
}),
} }
} }
} }

View File

@@ -30,12 +30,10 @@ use super::{FileTransferProtocol, ProtocolParams};
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
use crate::system::sshkey_storage::SshKeyStorage; use crate::system::sshkey_storage::SshKeyStorage;
use remotefs::client::{
aws_s3::AwsS3Fs,
ftp::FtpFs,
ssh::{ScpFs, SftpFs, SshOpts},
};
use remotefs::RemoteFs; use remotefs::RemoteFs;
use remotefs_aws_s3::AwsS3Fs;
use remotefs_ftp::FtpFs;
use remotefs_ssh::{ScpFs, SftpFs, SshOpts};
use std::path::PathBuf; use std::path::PathBuf;
/// Remotefs builder /// Remotefs builder

View File

@@ -28,17 +28,16 @@
// ext // ext
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use remotefs::fs::UnixPex; 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::fs::{self, File as StdFile, OpenOptions};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::SystemTime;
use thiserror::Error; use thiserror::Error;
use wildmatch::WildMatch; use wildmatch::WildMatch;
// Metadata ext // Metadata ext
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use std::fs::set_permissions; use std::fs::set_permissions;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs::PermissionsExt;
// Locals // Locals
use crate::utils::path; 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 /// It provides functions to navigate across the local host file system
pub struct Localhost { pub struct Localhost {
wrkdir: PathBuf, wrkdir: PathBuf,
files: Vec<Entry>, files: Vec<File>,
} }
impl Localhost { impl Localhost {
@@ -157,7 +156,7 @@ impl Localhost {
/// List files in current directory /// List files in current directory
#[allow(dead_code)] #[allow(dead_code)]
pub fn list_dir(&self) -> Vec<Entry> { pub fn list_dir(&self) -> Vec<File> {
self.files.clone() self.files.clone()
} }
@@ -245,71 +244,68 @@ impl Localhost {
} }
/// Remove file entry /// Remove file entry
pub fn remove(&mut self, entry: &Entry) -> Result<(), HostError> { pub fn remove(&mut self, entry: &File) -> Result<(), HostError> {
match entry { if entry.is_dir() {
Entry::Directory(dir) => { // If file doesn't exist; return error
// If file doesn't exist; return error debug!("Removing directory {}", entry.path().display());
debug!("Removing directory {}", dir.path.display()); if !entry.path().exists() {
if !dir.path.as_path().exists() { error!("Directory doesn't exist");
error!("Directory doesn't exist"); return Err(HostError::new(
return Err(HostError::new( HostErrorType::NoSuchFileOrDirectory,
HostErrorType::NoSuchFileOrDirectory, None,
None, entry.path(),
dir.path.as_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 Err(err) => {
match std::fs::remove_dir_all(dir.path.as_path()) { error!("Could not remove directory: {}", err);
Ok(_) => { Err(HostError::new(
// Update dir HostErrorType::DeleteFailed,
self.files = self.scan_dir(self.wrkdir.as_path())?; Some(err),
info!("Removed directory {}", dir.path.display()); entry.path(),
Ok(()) ))
}
Err(err) => {
error!("Could not remove directory: {}", err);
Err(HostError::new(
HostErrorType::DeleteFailed,
Some(err),
dir.path.as_path(),
))
}
} }
} }
Entry::File(file) => { } else {
// If file doesn't exist; return error // If file doesn't exist; return error
debug!("Removing file {}", file.path.display()); debug!("Removing file {}", entry.path().display());
if !file.path.as_path().exists() { if !entry.path().exists() {
error!("File doesn't exist"); error!("File doesn't exist");
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, None,
file.path.as_path(), 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 Err(err) => {
match std::fs::remove_file(file.path.as_path()) { error!("Could not remove file: {}", err);
Ok(_) => { Err(HostError::new(
// Update dir HostErrorType::DeleteFailed,
self.files = self.scan_dir(self.wrkdir.as_path())?; Some(err),
info!("Removed file {}", file.path.display()); entry.path(),
Ok(()) ))
}
Err(err) => {
error!("Could not remove file: {}", err);
Err(HostError::new(
HostErrorType::DeleteFailed,
Some(err),
file.path.as_path(),
))
}
} }
} }
} }
} }
/// Rename file or directory to new name /// 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) { match std::fs::rename(entry.path(), dst_path) {
Ok(_) => { Ok(_) => {
// Scan dir // Scan dir
@@ -338,7 +334,7 @@ impl Localhost {
} }
/// Copy file to destination path /// 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 // Get absolute path of dest
let dst: PathBuf = self.to_path(dst); let dst: PathBuf = self.to_path(dst);
info!( info!(
@@ -347,46 +343,43 @@ impl Localhost {
dst.display() dst.display()
); );
// Match entry // Match entry
match entry { if entry.is_dir() {
Entry::File(file) => { // If destination path doesn't exist, create destination
// Copy file if !dst.exists() {
// If destination path is a directory, push file name debug!("Directory {} doesn't exist; creating it", dst.display());
let dst: PathBuf = match dst.as_path().is_dir() { self.mkdir(dst.as_path())?;
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");
} }
Entry::Directory(dir) => { // Scan dir
// If destination path doesn't exist, create destination let dir_files: Vec<File> = self.scan_dir(entry.path())?;
if !dst.exists() { // Iterate files
debug!("Directory {} doesn't exist; creating it", dst.display()); for dir_entry in dir_files.iter() {
self.mkdir(dst.as_path())?; // Calculate dst
} let mut sub_dst: PathBuf = dst.clone();
// Scan dir sub_dst.push(dir_entry.name());
let dir_files: Vec<Entry> = self.scan_dir(dir.path.as_path())?; // Call function recursively
// Iterate files self.copy(dir_entry, sub_dst.as_path())?;
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 // Reload directory if dst is pwd
match dst.is_dir() { match dst.is_dir() {
@@ -412,9 +405,8 @@ impl Localhost {
Ok(()) Ok(())
} }
/// Stat file and create a Entry /// Stat file and create a File
#[cfg(target_family = "unix")] pub fn stat(&self, path: &Path) -> Result<File, HostError> {
pub fn stat(&self, path: &Path) -> Result<Entry, HostError> {
info!("Stating file {}", path.display()); info!("Stating file {}", path.display());
let path: PathBuf = self.to_path(path); let path: PathBuf = self.to_path(path);
let attr = match fs::metadata(path.as_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 // Match dir / file
let metadata = Metadata { Ok(File { path, 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<Entry, HostError> {
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,
})
}
})
} }
/// Execute a command on localhost /// Execute a command on localhost
@@ -644,11 +559,11 @@ impl Localhost {
} }
/// Get content of the current directory as a list of fs entry /// Get content of the current directory as a list of fs entry
pub fn scan_dir(&self, dir: &Path) -> Result<Vec<Entry>, HostError> { pub fn scan_dir(&self, dir: &Path) -> Result<Vec<File>, HostError> {
info!("Reading directory {}", dir.display()); info!("Reading directory {}", dir.display());
match std::fs::read_dir(dir) { match std::fs::read_dir(dir) {
Ok(e) => { Ok(e) => {
let mut fs_entries: Vec<Entry> = Vec::new(); let mut fs_entries: Vec<File> = Vec::new();
for entry in e.flatten() { for entry in e.flatten() {
// NOTE: 0.4.1, don't fail if stat for one file fails // NOTE: 0.4.1, don't fail if stat for one file fails
match self.stat(entry.path().as_path()) { 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. /// Find files matching `search` on localhost starting from current directory. Search supports recursive search of course.
/// The `search` argument supports wilcards ('*', '?') /// The `search` argument supports wilcards ('*', '?')
pub fn find(&self, search: &str) -> Result<Vec<Entry>, HostError> { pub fn find(&self, search: &str) -> Result<Vec<File>, HostError> {
self.iter_search(self.wrkdir.as_path(), &WildMatch::new(search)) self.iter_search(self.wrkdir.as_path(), &WildMatch::new(search))
} }
@@ -692,9 +607,9 @@ impl Localhost {
/// Recursive call for `find` method. /// Recursive call for `find` method.
/// Search in current directory for files which match `filter`. /// 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. /// 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<Vec<Entry>, HostError> { fn iter_search(&self, dir: &Path, filter: &WildMatch) -> Result<Vec<File>, HostError> {
// Scan directory // Scan directory
let mut drained: Vec<Entry> = Vec::new(); let mut drained: Vec<File> = Vec::new();
match self.scan_dir(dir) { match self.scan_dir(dir) {
Err(err) => Err(err), Err(err) => Err(err),
Ok(entries) => { Ok(entries) => {
@@ -705,20 +620,16 @@ impl Localhost {
- if is file: check if it matches `filter` - if is file: check if it matches `filter`
- if it matches `filter`: push to to filter - if it matches `filter`: push to to filter
*/ */
for entry in entries.iter() { for entry in entries.into_iter() {
match entry { if entry.is_dir() {
Entry::Directory(dir) => { // If directory matches; push directory to drained
// If directory matches; push directory to drained let next_path = entry.path().to_path_buf();
if filter.matches(dir.name.as_str()) { if filter.matches(entry.name().as_str()) {
drained.push(Entry::Directory(dir.clone())); drained.push(entry);
}
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()));
}
} }
drained.append(&mut self.iter_search(next_path.as_path(), filter)?);
} else if filter.matches(entry.name().as_str()) {
drained.push(entry);
} }
} }
Ok(drained) Ok(drained)
@@ -902,37 +813,27 @@ mod tests {
.is_ok()); .is_ok());
// Get dir // Get dir
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
// Verify files // Verify files
let file_0: &Entry = files.get(0).unwrap(); let file_0: &File = files.get(0).unwrap();
match file_0 { if file_0.name() == String::from("foo.txt") {
Entry::File(file_0) => { assert!(file_0.metadata.symlink.is_none());
if file_0.name == String::from("foo.txt") { } else {
assert!(file_0.metadata.symlink.is_none()); assert_eq!(
} else { file_0.metadata.symlink.as_ref().unwrap(),
assert_eq!( &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
file_0.metadata.symlink.as_ref().unwrap(), );
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) }
);
}
}
_ => panic!("expected entry 0 to be file: {:?}", file_0),
};
// Verify simlink // Verify simlink
let file_1: &Entry = files.get(1).unwrap(); let file_1: &File = files.get(1).unwrap();
match file_1 { if file_1.name() == String::from("bar.txt") {
Entry::File(file_1) => { assert_eq!(
if file_1.name == String::from("bar.txt") { file_1.metadata.symlink.as_ref().unwrap(),
assert_eq!( &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
file_1.metadata.symlink.as_ref().unwrap(), );
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) } else {
); assert!(file_1.metadata.symlink.is_none());
} else { }
assert!(file_1.metadata.symlink.is_none());
}
}
_ => panic!("expected entry 0 to be file: {:?}", file_1),
};
} }
#[test] #[test]
@@ -940,10 +841,10 @@ mod tests {
fn test_host_localhost_mkdir() { fn test_host_localhost_mkdir() {
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 0); // There should be 0 files now assert_eq!(files.len(), 0); // There should be 0 files now
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
// Try to re-create directory // Try to re-create directory
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err()); assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err());
@@ -967,17 +868,17 @@ mod tests {
// Create sample file // Create sample file
assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok()); 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 mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
// Remove file // Remove file
assert!(host.remove(files.get(0).unwrap()).is_ok()); assert!(host.remove(files.get(0).unwrap()).is_ok());
// There should be 0 files now // There should be 0 files now
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 0); // There should be 0 files now assert_eq!(files.len(), 0); // There should be 0 files now
// Create directory // Create directory
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
// Delete directory // Delete directory
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
assert!(host.remove(files.get(0).unwrap()).is_ok()); assert!(host.remove(files.get(0).unwrap()).is_ok());
// Remove unexisting directory // Remove unexisting directory
@@ -998,7 +899,7 @@ mod tests {
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str()); PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str());
assert!(StdFile::create(src_path.as_path()).is_ok()); assert!(StdFile::create(src_path.as_path()).is_ok());
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
assert_eq!(files.get(0).unwrap().name(), "foo.txt"); assert_eq!(files.get(0).unwrap().name(), "foo.txt");
// Rename file // Rename file
@@ -1008,7 +909,7 @@ mod tests {
.rename(files.get(0).unwrap(), dst_path.as_path()) .rename(files.get(0).unwrap(), dst_path.as_path())
.is_ok()); .is_ok());
// There should be still 1 file now, but named bar.txt // There should be still 1 file now, but named bar.txt
let files: Vec<Entry> = host.list_dir(); let files: Vec<File> = host.list_dir();
assert_eq!(files.len(), 1); // There should be 0 files now assert_eq!(files.len(), 1); // There should be 0 files now
assert_eq!(files.get(0).unwrap().name(), "bar.txt"); assert_eq!(files.get(0).unwrap().name(), "bar.txt");
// Fail // Fail
@@ -1052,7 +953,7 @@ mod tests {
file2_path.push("bar.txt"); file2_path.push("bar.txt");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); 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")); assert_eq!(file1_entry.name(), String::from("foo.txt"));
// Copy // Copy
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); 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"); let file2_path: PathBuf = PathBuf::from("bar.txt");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); 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")); assert_eq!(file1_entry.name(), String::from("foo.txt"));
// Copy // Copy
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
@@ -1108,7 +1009,7 @@ mod tests {
dir_dest.push("test_dest_dir/"); dir_dest.push("test_dest_dir/");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); 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")); assert_eq!(dir_src_entry.name(), String::from("test_dir"));
// Copy // Copy
assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); 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/"); let dir_dest: PathBuf = PathBuf::from("test_dest_dir/");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); 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")); assert_eq!(dir_src_entry.name(), String::from("test_dir"));
// Copy // Copy
assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); 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()); assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok());
let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap();
// Find txt files // Find txt files
let mut result: Vec<Entry> = host.find("*.txt").ok().unwrap(); let mut result: Vec<File> = host.find("*.txt").ok().unwrap();
result.sort_by_key(|x: &Entry| x.name().to_lowercase()); result.sort_by_key(|x: &File| x.name().to_lowercase());
// There should be 3 entries // There should be 3 entries
assert_eq!(result.len(), 3); assert_eq!(result.len(), 3);
// Check names (they should be sorted alphabetically already; NOTE: examples/ comes before pippo.txt) // 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[1].name(), "omar.txt");
assert_eq!(result[2].name(), "pippo.txt"); assert_eq!(result[2].name(), "pippo.txt");
// Search for directory // Search for directory
let mut result: Vec<Entry> = host.find("examples*").ok().unwrap(); let mut result: Vec<File> = host.find("examples*").ok().unwrap();
result.sort_by_key(|x: &Entry| x.name().to_lowercase()); result.sort_by_key(|x: &File| x.name().to_lowercase());
assert_eq!(result.len(), 2); assert_eq!(result.len(), 2);
assert_eq!(result[0].name(), "examples"); assert_eq!(result[0].name(), "examples");
assert_eq!(result[1].name(), "examples.csv"); assert_eq!(result[1].name(), "examples.csv");

View File

@@ -28,7 +28,7 @@
// Locals // Locals
use super::config_client::ConfigClient; use super::config_client::ConfigClient;
// Ext // Ext
use remotefs::client::ssh::SshKeyStorage as SshKeyStorageT; use remotefs_ssh::SshKeyStorage as SshKeyStorageT;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};

View File

@@ -28,7 +28,7 @@
// locals // locals
use super::{FileExplorerTab, FileTransferActivity, LogLevel, Msg, PendingActionMsg}; use super::{FileExplorerTab, FileTransferActivity, LogLevel, Msg, PendingActionMsg};
use remotefs::Directory; use remotefs::File;
use std::path::PathBuf; use std::path::PathBuf;
/// Describes destination for sync browsing /// Describes destination for sync browsing
@@ -40,18 +40,18 @@ enum SyncBrowsingDestination {
impl FileTransferActivity { impl FileTransferActivity {
/// Enter a directory on local host from entry /// Enter a directory on local host from entry
pub(crate) fn action_enter_local_dir(&mut self, dir: Directory) { pub(crate) fn action_enter_local_dir(&mut self, dir: File) {
self.local_changedir(dir.path.as_path(), true); self.local_changedir(dir.path(), true);
if self.browser.sync_browsing && self.browser.found().is_none() { 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 /// Enter a directory on local host from entry
pub(crate) fn action_enter_remote_dir(&mut self, dir: Directory) { pub(crate) fn action_enter_remote_dir(&mut self, dir: File) {
self.remote_changedir(dir.path.as_path(), true); self.remote_changedir(dir.path(), true);
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name)); self.synchronize_browsing(SyncBrowsingDestination::Path(dir.name()));
} }
} }

View File

@@ -26,20 +26,20 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // 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}; use std::path::{Path, PathBuf};
impl FileTransferActivity { impl FileTransferActivity {
/// Copy file on local /// Copy file on local
pub(crate) fn action_local_copy(&mut self, input: String) { pub(crate) fn action_local_copy(&mut self, input: String) {
match self.get_local_selected_entries() { match self.get_local_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
let dest_path: PathBuf = PathBuf::from(input); let dest_path: PathBuf = PathBuf::from(input);
self.local_copy_file(&entry, dest_path.as_path()); self.local_copy_file(&entry, dest_path.as_path());
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Try to copy each file to Input/{FILE_NAME} // Try to copy each file to Input/{FILE_NAME}
let base_path: PathBuf = PathBuf::from(input); let base_path: PathBuf = PathBuf::from(input);
// Iter files // Iter files
@@ -49,18 +49,18 @@ impl FileTransferActivity {
self.local_copy_file(entry, dest_path.as_path()); self.local_copy_file(entry, dest_path.as_path());
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
/// Copy file on remote /// Copy file on remote
pub(crate) fn action_remote_copy(&mut self, input: String) { pub(crate) fn action_remote_copy(&mut self, input: String) {
match self.get_remote_selected_entries() { match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
let dest_path: PathBuf = PathBuf::from(input); let dest_path: PathBuf = PathBuf::from(input);
self.remote_copy_file(entry, dest_path.as_path()); self.remote_copy_file(entry, dest_path.as_path());
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Try to copy each file to Input/{FILE_NAME} // Try to copy each file to Input/{FILE_NAME}
let base_path: PathBuf = PathBuf::from(input); let base_path: PathBuf = PathBuf::from(input);
// Iter files // Iter files
@@ -70,11 +70,11 @@ impl FileTransferActivity {
self.remote_copy_file(entry, dest_path.as_path()); 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) { match self.host.copy(entry, dest) {
Ok(_) => { Ok(_) => {
self.log( 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) { match self.client.as_mut().copy(entry.path(), dest) {
Ok(_) => { Ok(_) => {
self.log( self.log(
@@ -129,123 +129,121 @@ impl FileTransferActivity {
} }
/// Tricky copy will be used whenever copy command is not available on remote host /// 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 // NOTE: VERY IMPORTANT; wait block must be umounted or something really bad will happen
self.umount_wait(); self.umount_wait();
// match entry // match entry
match entry { if entry.is_dir() {
Entry::File(entry) => { let tempdir: tempfile::TempDir = match tempfile::TempDir::new() {
// Create tempfile Ok(d) => d,
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() { Err(err) => {
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))
{
self.log_and_alert( self.log_and_alert(
LogLevel::Error, 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()) { // Get path of dest
Ok(e) => e.unwrap_file(), let mut tempdir_path: PathBuf = tempdir.path().to_path_buf();
Err(err) => { tempdir_path.push(entry.name());
self.log_and_alert( // Download file
LogLevel::Error, if let Err(err) =
format!( self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None)
"Copy failed: could not stat \"{}\": {}", {
tmpfile.path().display(), self.log_and_alert(
err LogLevel::Error,
), format!("Copy failed: failed to download file: {}", err),
); );
return Err(err.to_string()); return Err(err);
} }
}; // Stat dir
// Upload file to destination let tempdir_entry = match self.host.stat(tempdir_path.as_path()) {
let wrkdir = self.remote().wrkdir.clone(); Ok(e) => e,
if let Err(err) = self.filetransfer_send( Err(err) => {
TransferPayload::File(tmpfile_entry),
wrkdir.as_path(),
Some(String::from(dest.to_string_lossy())),
) {
self.log_and_alert( self.log_and_alert(
LogLevel::Error, LogLevel::Error,
format!( format!(
"Copy failed: could not write file {}: {}", "Copy failed: could not stat \"{}\": {}",
entry_path.display(), tempdir.path().display(),
err 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(_) => { Ok(())
let tempdir: tempfile::TempDir = match tempfile::TempDir::new() { } else {
Ok(d) => d, // Create tempfile
Err(err) => { let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
self.log_and_alert( Ok(f) => f,
LogLevel::Error, Err(err) => {
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)
{
self.log_and_alert( self.log_and_alert(
LogLevel::Error, 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()) { // Download file
Ok(e) => e, let name = entry.name();
Err(err) => { let entry_path = entry.path().to_path_buf();
self.log_and_alert( if let Err(err) =
LogLevel::Error, self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name))
format!( {
"Copy failed: could not stat \"{}\": {}", self.log_and_alert(
tempdir.path().display(), LogLevel::Error,
err format!("Copy failed: could not download to temporary file: {}", err),
), );
); return Err(err);
return Err(err.to_string()); }
} // Get local fs entry
}; let tmpfile_entry = match self.host.stat(tmpfile.path()) {
// Upload to destination Ok(e) if e.is_file() => e,
let wrkdir: PathBuf = self.remote().wrkdir.clone(); Ok(_) => panic!("{} is not a file", tmpfile.path().display()),
if let Err(err) = self.filetransfer_send( Err(err) => {
TransferPayload::Any(tempdir_entry),
wrkdir.as_path(),
Some(String::from(dest.to_string_lossy())),
) {
self.log_and_alert( self.log_and_alert(
LogLevel::Error, 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(())
} }
} }
} }

View File

@@ -26,46 +26,46 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{FileTransferActivity, LogLevel, SelectedEntry}; use super::{FileTransferActivity, LogLevel, SelectedFile};
use remotefs::Entry; use remotefs::File;
impl FileTransferActivity { impl FileTransferActivity {
pub(crate) fn action_local_delete(&mut self) { pub(crate) fn action_local_delete(&mut self) {
match self.get_local_selected_entries() { match self.get_local_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
// Delete file // Delete file
self.local_remove_file(&entry); self.local_remove_file(&entry);
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Iter files // Iter files
for entry in entries.iter() { for entry in entries.iter() {
// Delete file // Delete file
self.local_remove_file(entry); self.local_remove_file(entry);
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
pub(crate) fn action_remote_delete(&mut self) { pub(crate) fn action_remote_delete(&mut self) {
match self.get_remote_selected_entries() { match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
// Delete file // Delete file
self.remote_remove_file(&entry); self.remote_remove_file(&entry);
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Iter files // Iter files
for entry in entries.iter() { for entry in entries.iter() {
// Delete file // Delete file
self.remote_remove_file(entry); 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) { match self.host.remove(entry) {
Ok(_) => { Ok(_) => {
// Log // 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()) { match self.client.remove_dir_all(entry.path()) {
Ok(_) => { Ok(_) => {
self.log( self.log(

View File

@@ -26,10 +26,10 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload}; use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload};
// ext // ext
use remotefs::{Entry, File}; use remotefs::File;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Read; use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -37,10 +37,10 @@ use std::time::SystemTime;
impl FileTransferActivity { impl FileTransferActivity {
pub(crate) fn action_edit_local_file(&mut self) { pub(crate) fn action_edit_local_file(&mut self) {
let entries: Vec<Entry> = match self.get_local_selected_entries() { let entries: Vec<File> = match self.get_local_selected_entries() {
SelectedEntry::One(entry) => vec![entry], SelectedFile::One(entry) => vec![entry],
SelectedEntry::Many(entries) => entries, SelectedFile::Many(entries) => entries,
SelectedEntry::None => vec![], SelectedFile::None => vec![],
}; };
// Edit all entries // Edit all entries
for entry in entries.iter() { for entry in entries.iter() {
@@ -59,21 +59,21 @@ impl FileTransferActivity {
} }
pub(crate) fn action_edit_remote_file(&mut self) { pub(crate) fn action_edit_remote_file(&mut self) {
let entries: Vec<Entry> = match self.get_remote_selected_entries() { let entries: Vec<File> = match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => vec![entry], SelectedFile::One(entry) => vec![entry],
SelectedEntry::Many(entries) => entries, SelectedFile::Many(entries) => entries,
SelectedEntry::None => vec![], SelectedFile::None => vec![],
}; };
// Edit all entries // Edit all entries
for entry in entries.into_iter() { for entry in entries.into_iter() {
// Check if file // Check if file
if let Entry::File(file) = entry { if entry.is_file() {
self.log( self.log(
LogLevel::Info, LogLevel::Info,
format!("Opening file \"{}\"", file.path.display()), format!("Opening file \"{}\"", entry.path().display()),
); );
// Edit file // 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); self.log_and_alert(LogLevel::Error, err);
} }
} }
@@ -149,8 +149,8 @@ impl FileTransferActivity {
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
// Download file // Download file
let file_name = file.name.clone(); let file_name = file.name();
let file_path = file.path.clone(); let file_path = file.path().to_path_buf();
if let Err(err) = self.filetransfer_recv( if let Err(err) = self.filetransfer_recv(
TransferPayload::File(file), TransferPayload::File(file),
tmpfile.as_path(), tmpfile.as_path(),
@@ -160,7 +160,7 @@ impl FileTransferActivity {
} }
// Get current file modification time // Get current file modification time
let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) { 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) => { Err(err) => {
return Err(format!( return Err(format!(
"Could not stat \"{}\": {}", "Could not stat \"{}\": {}",
@@ -174,7 +174,7 @@ impl FileTransferActivity {
return Err(err); return Err(err);
} }
// Get local fs entry // 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, Ok(e) => e,
Err(err) => { Err(err) => {
return Err(format!( return Err(format!(
@@ -185,7 +185,12 @@ impl FileTransferActivity {
} }
}; };
// Check if file has changed // 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 => { true => {
self.log( self.log(
LogLevel::Info, LogLevel::Info,
@@ -196,7 +201,7 @@ impl FileTransferActivity {
); );
// Get local fs entry // Get local fs entry
let tmpfile_entry = match self.host.stat(tmpfile.as_path()) { let tmpfile_entry = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e.unwrap_file(), Ok(e) => e,
Err(err) => { Err(err) => {
return Err(format!( return Err(format!(
"Could not stat \"{}\": {}", "Could not stat \"{}\": {}",

View File

@@ -27,19 +27,19 @@
*/ */
// locals // locals
use super::super::browser::FileExplorerTab; 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; use std::path::PathBuf;
impl FileTransferActivity { impl FileTransferActivity {
pub(crate) fn action_local_find(&mut self, input: String) -> Result<Vec<Entry>, String> { pub(crate) fn action_local_find(&mut self, input: String) -> Result<Vec<File>, String> {
match self.host.find(input.as_str()) { match self.host.find(input.as_str()) {
Ok(entries) => Ok(entries), Ok(entries) => Ok(entries),
Err(err) => Err(format!("Could not search for files: {}", err)), Err(err) => Err(format!("Could not search for files: {}", err)),
} }
} }
pub(crate) fn action_remote_find(&mut self, input: String) -> Result<Vec<Entry>, String> { pub(crate) fn action_remote_find(&mut self, input: String) -> Result<Vec<File>, String> {
match self.client.as_mut().find(input.as_str()) { match self.client.as_mut().find(input.as_str()) {
Ok(entries) => Ok(entries), Ok(entries) => Ok(entries),
Err(err) => Err(format!("Could not search for files: {}", err)), Err(err) => Err(format!("Could not search for files: {}", err)),
@@ -48,14 +48,15 @@ impl FileTransferActivity {
pub(crate) fn action_find_changedir(&mut self) { pub(crate) fn action_find_changedir(&mut self) {
// Match entry // 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 // Get path: if a directory, use directory path; if it is a File, get parent path
let path: PathBuf = match entry { let path = if entry.is_dir() {
Entry::Directory(dir) => dir.path, entry.path().to_path_buf()
Entry::File(file) => match file.path.parent() { } else {
match entry.path().parent() {
None => PathBuf::from("."), None => PathBuf::from("."),
Some(p) => p.to_path_buf(), Some(p) => p.to_path_buf(),
}, }
}; };
// Change directory // Change directory
match self.browser.tab() { match self.browser.tab() {
@@ -75,13 +76,13 @@ impl FileTransferActivity {
FileExplorerTab::FindRemote | FileExplorerTab::Remote => self.local().wrkdir.clone(), FileExplorerTab::FindRemote | FileExplorerTab::Remote => self.local().wrkdir.clone(),
}; };
match self.get_found_selected_entries() { match self.get_found_selected_entries() {
SelectedEntry::One(entry) => match self.browser.tab() { SelectedFile::One(entry) => match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => { FileExplorerTab::FindLocal | FileExplorerTab::Local => {
let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref()); let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
if self.config().get_prompt_on_file_replace() if self.config().get_prompt_on_file_replace()
&& self.remote_file_exists(file_to_check.as_path()) && self.remote_file_exists(file_to_check.as_path())
&& !self.should_replace_file( && !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 // Do not replace
@@ -103,7 +104,7 @@ impl FileTransferActivity {
if self.config().get_prompt_on_file_replace() if self.config().get_prompt_on_file_replace()
&& self.local_file_exists(file_to_check.as_path()) && self.local_file_exists(file_to_check.as_path())
&& !self.should_replace_file( && !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 // 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 // In case of selection: save multiple files in wrkdir/input
let mut dest_path: PathBuf = wrkdir; let mut dest_path: PathBuf = wrkdir;
if let Some(save_as) = opts.save_as { if let Some(save_as) = opts.save_as {
@@ -132,7 +133,7 @@ impl FileTransferActivity {
FileExplorerTab::FindLocal | FileExplorerTab::Local => { FileExplorerTab::FindLocal | FileExplorerTab::Local => {
if self.config().get_prompt_on_file_replace() { if self.config().get_prompt_on_file_replace() {
// Check which file would be replaced // Check which file would be replaced
let existing_files: Vec<&Entry> = entries let existing_files: Vec<&File> = entries
.iter() .iter()
.filter(|x| { .filter(|x| {
self.remote_file_exists( self.remote_file_exists(
@@ -163,7 +164,7 @@ impl FileTransferActivity {
FileExplorerTab::FindRemote | FileExplorerTab::Remote => { FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
if self.config().get_prompt_on_file_replace() { if self.config().get_prompt_on_file_replace() {
// Check which file would be replaced // Check which file would be replaced
let existing_files: Vec<&Entry> = entries let existing_files: Vec<&File> = entries
.iter() .iter()
.filter(|x| { .filter(|x| {
self.local_file_exists( self.local_file_exists(
@@ -191,28 +192,28 @@ impl FileTransferActivity {
} }
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
pub(crate) fn action_find_delete(&mut self) { pub(crate) fn action_find_delete(&mut self) {
match self.get_found_selected_entries() { match self.get_found_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
// Delete file // Delete file
self.remove_found_file(&entry); self.remove_found_file(&entry);
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Iter files // Iter files
for entry in entries.iter() { for entry in entries.iter() {
// Delete file // Delete file
self.remove_found_file(entry); 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() { match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => { FileExplorerTab::FindLocal | FileExplorerTab::Local => {
self.local_remove_file(entry); self.local_remove_file(entry);
@@ -225,39 +226,39 @@ impl FileTransferActivity {
pub(crate) fn action_find_open(&mut self) { pub(crate) fn action_find_open(&mut self) {
match self.get_found_selected_entries() { match self.get_found_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
// Open file // Open file
self.open_found_file(&entry, None); self.open_found_file(&entry, None);
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Iter files // Iter files
for entry in entries.iter() { for entry in entries.iter() {
// Open file // Open file
self.open_found_file(entry, None); self.open_found_file(entry, None);
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
pub(crate) fn action_find_open_with(&mut self, with: &str) { pub(crate) fn action_find_open_with(&mut self, with: &str) {
match self.get_found_selected_entries() { match self.get_found_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
// Open file // Open file
self.open_found_file(&entry, Some(with)); self.open_found_file(&entry, Some(with));
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Iter files // Iter files
for entry in entries.iter() { for entry in entries.iter() {
// Open file // Open file
self.open_found_file(entry, Some(with)); 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() { match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => { FileExplorerTab::FindLocal | FileExplorerTab::Local => {
self.action_open_local_file(entry, with); self.action_open_local_file(entry, with);

View File

@@ -29,7 +29,7 @@ pub(self) use super::{
browser::FileExplorerTab, FileTransferActivity, Id, LogLevel, Msg, PendingActionMsg, browser::FileExplorerTab, FileTransferActivity, Id, LogLevel, Msg, PendingActionMsg,
TransferOpts, TransferPayload, TransferOpts, TransferPayload,
}; };
pub(self) use remotefs::Entry; pub(self) use remotefs::File;
use tuirealm::{State, StateValue}; use tuirealm::{State, StateValue};
// actions // actions
@@ -49,100 +49,100 @@ pub(crate) mod submit;
pub(crate) mod symlink; pub(crate) mod symlink;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum SelectedEntry { pub(crate) enum SelectedFile {
One(Entry), One(File),
Many(Vec<Entry>), Many(Vec<File>),
None, None,
} }
#[derive(Debug)] #[derive(Debug)]
enum SelectedEntryIndex { enum SelectedFileIndex {
One(usize), One(usize),
Many(Vec<usize>), Many(Vec<usize>),
None, None,
} }
impl From<Option<&Entry>> for SelectedEntry { impl From<Option<&File>> for SelectedFile {
fn from(opt: Option<&Entry>) -> Self { fn from(opt: Option<&File>) -> Self {
match opt { match opt {
Some(e) => SelectedEntry::One(e.clone()), Some(e) => SelectedFile::One(e.clone()),
None => SelectedEntry::None, None => SelectedFile::None,
} }
} }
} }
impl From<Vec<&Entry>> for SelectedEntry { impl From<Vec<&File>> for SelectedFile {
fn from(files: Vec<&Entry>) -> Self { fn from(files: Vec<&File>) -> Self {
SelectedEntry::Many(files.into_iter().cloned().collect()) SelectedFile::Many(files.into_iter().cloned().collect())
} }
} }
impl FileTransferActivity { impl FileTransferActivity {
/// Get local file entry /// 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) { match self.get_selected_index(&Id::ExplorerLocal) {
SelectedEntryIndex::One(idx) => SelectedEntry::from(self.local().get(idx)), SelectedFileIndex::One(idx) => SelectedFile::from(self.local().get(idx)),
SelectedEntryIndex::Many(files) => { SelectedFileIndex::Many(files) => {
let files: Vec<&Entry> = files let files: Vec<&File> = files
.iter() .iter()
.map(|x| self.local().get(*x)) // Usize to Option<Entry> .map(|x| self.local().get(*x)) // Usize to Option<File>
.flatten() .flatten()
.collect(); .collect();
SelectedEntry::from(files) SelectedFile::from(files)
} }
SelectedEntryIndex::None => SelectedEntry::None, SelectedFileIndex::None => SelectedFile::None,
} }
} }
/// Get remote file entry /// 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) { match self.get_selected_index(&Id::ExplorerRemote) {
SelectedEntryIndex::One(idx) => SelectedEntry::from(self.remote().get(idx)), SelectedFileIndex::One(idx) => SelectedFile::from(self.remote().get(idx)),
SelectedEntryIndex::Many(files) => { SelectedFileIndex::Many(files) => {
let files: Vec<&Entry> = files let files: Vec<&File> = files
.iter() .iter()
.map(|x| self.remote().get(*x)) // Usize to Option<Entry> .map(|x| self.remote().get(*x)) // Usize to Option<File>
.flatten() .flatten()
.collect(); .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 /// Returns whether only one entry is selected on local host
pub(crate) fn is_local_selected_one(&self) -> bool { 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 /// Returns whether only one entry is selected on remote host
pub(crate) fn is_remote_selected_one(&self) -> bool { 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 /// 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) { match self.get_selected_index(&Id::ExplorerFind) {
SelectedEntryIndex::One(idx) => { SelectedFileIndex::One(idx) => {
SelectedEntry::from(self.found().as_ref().unwrap().get(idx)) SelectedFile::from(self.found().as_ref().unwrap().get(idx))
} }
SelectedEntryIndex::Many(files) => { SelectedFileIndex::Many(files) => {
let files: Vec<&Entry> = files let files: Vec<&File> = files
.iter() .iter()
.map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option<Entry> .map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option<File>
.flatten() .flatten()
.collect(); .collect();
SelectedEntry::from(files) SelectedFile::from(files)
} }
SelectedEntryIndex::None => SelectedEntry::None, SelectedFileIndex::None => SelectedFile::None,
} }
} }
// -- private // -- private
fn get_selected_index(&self, id: &Id) -> SelectedEntryIndex { fn get_selected_index(&self, id: &Id) -> SelectedFileIndex {
match self.app.state(id) { 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)) => { Ok(State::Vec(files)) => {
let list: Vec<usize> = files let list: Vec<usize> = files
.iter() .iter()
@@ -151,9 +151,9 @@ impl FileTransferActivity {
_ => 0, _ => 0,
}) })
.collect(); .collect();
SelectedEntryIndex::Many(list) SelectedFileIndex::Many(list)
} }
_ => SelectedEntryIndex::None, _ => SelectedFileIndex::None,
} }
} }
} }

View File

@@ -26,8 +26,8 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{Entry, FileTransferActivity, LogLevel}; use super::{File, FileTransferActivity, LogLevel};
use std::fs::File; use std::fs::File as StdFile;
use std::path::PathBuf; use std::path::PathBuf;
impl FileTransferActivity { impl FileTransferActivity {
@@ -86,7 +86,7 @@ impl FileTransferActivity {
), ),
Ok(tfile) => { Ok(tfile) => {
// Stat tempfile // Stat tempfile
let local_file: Entry = match self.host.stat(tfile.path()) { let local_file: File = match self.host.stat(tfile.path()) {
Err(err) => { Err(err) => {
self.log_and_alert( self.log_and_alert(
LogLevel::Error, LogLevel::Error,
@@ -96,9 +96,9 @@ impl FileTransferActivity {
} }
Ok(f) => f, Ok(f) => f,
}; };
if let Entry::File(local_file) = local_file { if local_file.is_file() {
// Create file // Create file
let reader = Box::new(match File::open(tfile.path()) { let reader = Box::new(match StdFile::open(tfile.path()) {
Ok(f) => f, Ok(f) => f,
Err(err) => { Err(err) => {
self.log_and_alert( self.log_and_alert(

View File

@@ -26,17 +26,17 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{Entry, FileTransferActivity, LogLevel, SelectedEntry, TransferPayload}; use super::{File, FileTransferActivity, LogLevel, SelectedFile, TransferPayload};
// ext // ext
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
impl FileTransferActivity { impl FileTransferActivity {
/// Open local file /// Open local file
pub(crate) fn action_open_local(&mut self) { pub(crate) fn action_open_local(&mut self) {
let entries: Vec<Entry> = match self.get_local_selected_entries() { let entries: Vec<File> = match self.get_local_selected_entries() {
SelectedEntry::One(entry) => vec![entry], SelectedFile::One(entry) => vec![entry],
SelectedEntry::Many(entries) => entries, SelectedFile::Many(entries) => entries,
SelectedEntry::None => vec![], SelectedFile::None => vec![],
}; };
entries entries
.iter() .iter()
@@ -45,10 +45,10 @@ impl FileTransferActivity {
/// Open local file /// Open local file
pub(crate) fn action_open_remote(&mut self) { pub(crate) fn action_open_remote(&mut self) {
let entries: Vec<Entry> = match self.get_remote_selected_entries() { let entries: Vec<File> = match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => vec![entry], SelectedFile::One(entry) => vec![entry],
SelectedEntry::Many(entries) => entries, SelectedFile::Many(entries) => entries,
SelectedEntry::None => vec![], SelectedFile::None => vec![],
}; };
entries entries
.iter() .iter()
@@ -56,20 +56,21 @@ impl FileTransferActivity {
} }
/// Perform open lopcal file /// 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); self.open_path_with(entry.path(), open_with);
} }
/// Open remote file. The file is first downloaded to a temporary directory on localhost /// 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 // Download file
let tmpfile: String = match self.get_cache_tmp_name(entry.name(), entry.extension()) { let tmpfile: String =
None => { match self.get_cache_tmp_name(&entry.name(), entry.extension().as_deref()) {
self.log(LogLevel::Error, String::from("Could not create tempdir")); None => {
return; self.log(LogLevel::Error, String::from("Could not create tempdir"));
} return;
Some(p) => p, }
}; Some(p) => p,
};
let cache: PathBuf = match self.cache.as_ref() { let cache: PathBuf = match self.cache.as_ref() {
None => { None => {
self.log(LogLevel::Error, String::from("Could not create tempdir")); self.log(LogLevel::Error, String::from("Could not create tempdir"));
@@ -101,10 +102,10 @@ impl FileTransferActivity {
/// Open selected file with provided application /// Open selected file with provided application
pub(crate) fn action_local_open_with(&mut self, with: &str) { pub(crate) fn action_local_open_with(&mut self, with: &str) {
let entries: Vec<Entry> = match self.get_local_selected_entries() { let entries: Vec<File> = match self.get_local_selected_entries() {
SelectedEntry::One(entry) => vec![entry], SelectedFile::One(entry) => vec![entry],
SelectedEntry::Many(entries) => entries, SelectedFile::Many(entries) => entries,
SelectedEntry::None => vec![], SelectedFile::None => vec![],
}; };
// Open all entries // Open all entries
entries entries
@@ -114,10 +115,10 @@ impl FileTransferActivity {
/// Open selected file with provided application /// Open selected file with provided application
pub(crate) fn action_remote_open_with(&mut self, with: &str) { pub(crate) fn action_remote_open_with(&mut self, with: &str) {
let entries: Vec<Entry> = match self.get_remote_selected_entries() { let entries: Vec<File> = match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => vec![entry], SelectedFile::One(entry) => vec![entry],
SelectedEntry::Many(entries) => entries, SelectedFile::Many(entries) => entries,
SelectedEntry::None => vec![], SelectedFile::None => vec![],
}; };
// Open all entries // Open all entries
entries entries

View File

@@ -26,7 +26,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{Entry, FileTransferActivity, LogLevel, SelectedEntry}; use super::{File, FileTransferActivity, LogLevel, SelectedFile};
use remotefs::RemoteErrorType; use remotefs::RemoteErrorType;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -34,11 +34,11 @@ use std::path::{Path, PathBuf};
impl FileTransferActivity { impl FileTransferActivity {
pub(crate) fn action_local_rename(&mut self, input: String) { pub(crate) fn action_local_rename(&mut self, input: String) {
match self.get_local_selected_entries() { match self.get_local_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
let dest_path: PathBuf = PathBuf::from(input); let dest_path: PathBuf = PathBuf::from(input);
self.local_rename_file(&entry, dest_path.as_path()); self.local_rename_file(&entry, dest_path.as_path());
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Try to copy each file to Input/{FILE_NAME} // Try to copy each file to Input/{FILE_NAME}
let base_path: PathBuf = PathBuf::from(input); let base_path: PathBuf = PathBuf::from(input);
// Iter files // Iter files
@@ -48,17 +48,17 @@ impl FileTransferActivity {
self.local_rename_file(entry, dest_path.as_path()); self.local_rename_file(entry, dest_path.as_path());
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
pub(crate) fn action_remote_rename(&mut self, input: String) { pub(crate) fn action_remote_rename(&mut self, input: String) {
match self.get_remote_selected_entries() { match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => { SelectedFile::One(entry) => {
let dest_path: PathBuf = PathBuf::from(input); let dest_path: PathBuf = PathBuf::from(input);
self.remote_rename_file(&entry, dest_path.as_path()); self.remote_rename_file(&entry, dest_path.as_path());
} }
SelectedEntry::Many(entries) => { SelectedFile::Many(entries) => {
// Try to copy each file to Input/{FILE_NAME} // Try to copy each file to Input/{FILE_NAME}
let base_path: PathBuf = PathBuf::from(input); let base_path: PathBuf = PathBuf::from(input);
// Iter files // Iter files
@@ -68,11 +68,11 @@ impl FileTransferActivity {
self.remote_rename_file(entry, dest_path.as_path()); 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) { match self.host.rename(entry, dest) {
Ok(_) => { Ok(_) => {
self.log( 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) { match self.client.as_mut().mov(entry.path(), dest) {
Ok(_) => { Ok(_) => {
self.log( self.log(
@@ -125,7 +125,7 @@ impl FileTransferActivity {
/// Tricky move will be used whenever copy command is not available on remote host. /// 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`) /// 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!( debug!(
"Using tricky-move to move entry {} to {}", "Using tricky-move to move entry {} to {}",
entry.path().display(), entry.path().display(),

View File

@@ -27,7 +27,7 @@
*/ */
// locals // locals
use super::{ use super::{
Entry, FileTransferActivity, LogLevel, Msg, PendingActionMsg, SelectedEntry, TransferOpts, File, FileTransferActivity, LogLevel, Msg, PendingActionMsg, SelectedFile, TransferOpts,
TransferPayload, TransferPayload,
}; };
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -52,19 +52,18 @@ impl FileTransferActivity {
fn local_send_file(&mut self, opts: TransferOpts) { fn local_send_file(&mut self, opts: TransferOpts) {
let wrkdir: PathBuf = self.remote().wrkdir.clone(); let wrkdir: PathBuf = self.remote().wrkdir.clone();
match self.get_local_selected_entries() { 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()); let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
if self.config().get_prompt_on_file_replace() if self.config().get_prompt_on_file_replace()
&& self.remote_file_exists(file_to_check.as_path()) && self.remote_file_exists(file_to_check.as_path())
&& !self.should_replace_file( && !self
opts.save_as.as_deref().unwrap_or_else(|| entry.name()), .should_replace_file(opts.save_as.clone().unwrap_or_else(|| entry.name()))
)
{ {
// Do not replace // Do not replace
return; return;
} }
if let Err(err) = self.filetransfer_send( if let Err(err) = self.filetransfer_send(
TransferPayload::Any(entry.clone()), TransferPayload::Any(entry),
wrkdir.as_path(), wrkdir.as_path(),
opts.save_as, 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 // In case of selection: save multiple files in wrkdir/input
let mut dest_path: PathBuf = wrkdir; let mut dest_path: PathBuf = wrkdir;
if let Some(save_as) = opts.save_as { if let Some(save_as) = opts.save_as {
@@ -85,7 +84,7 @@ impl FileTransferActivity {
// Iter files // Iter files
if self.config().get_prompt_on_file_replace() { if self.config().get_prompt_on_file_replace() {
// Check which file would be replaced // Check which file would be replaced
let existing_files: Vec<&Entry> = entries let existing_files: Vec<&File> = entries
.iter() .iter()
.filter(|x| { .filter(|x| {
self.remote_file_exists( self.remote_file_exists(
@@ -111,25 +110,24 @@ impl FileTransferActivity {
} }
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
fn remote_recv_file(&mut self, opts: TransferOpts) { fn remote_recv_file(&mut self, opts: TransferOpts) {
let wrkdir: PathBuf = self.local().wrkdir.clone(); let wrkdir: PathBuf = self.local().wrkdir.clone();
match self.get_remote_selected_entries() { 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()); let file_to_check = Self::file_to_check(&entry, opts.save_as.as_ref());
if self.config().get_prompt_on_file_replace() if self.config().get_prompt_on_file_replace()
&& self.local_file_exists(file_to_check.as_path()) && self.local_file_exists(file_to_check.as_path())
&& !self.should_replace_file( && !self
opts.save_as.as_deref().unwrap_or_else(|| entry.name()), .should_replace_file(opts.save_as.clone().unwrap_or_else(|| entry.name()))
)
{ {
return; return;
} }
if let Err(err) = self.filetransfer_recv( if let Err(err) = self.filetransfer_recv(
TransferPayload::Any(entry.clone()), TransferPayload::Any(entry),
wrkdir.as_path(), wrkdir.as_path(),
opts.save_as, 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 // In case of selection: save multiple files in wrkdir/input
let mut dest_path: PathBuf = wrkdir; let mut dest_path: PathBuf = wrkdir;
if let Some(save_as) = opts.save_as { if let Some(save_as) = opts.save_as {
@@ -150,7 +148,7 @@ impl FileTransferActivity {
// Iter files // Iter files
if self.config().get_prompt_on_file_replace() { if self.config().get_prompt_on_file_replace() {
// Check which file would be replaced // Check which file would be replaced
let existing_files: Vec<&Entry> = entries let existing_files: Vec<&File> = entries
.iter() .iter()
.filter(|x| { .filter(|x| {
self.local_file_exists( self.local_file_exists(
@@ -176,13 +174,13 @@ impl FileTransferActivity {
} }
} }
} }
SelectedEntry::None => {} SelectedFile::None => {}
} }
} }
/// Set pending transfer into storage /// Set pending transfer into storage
pub(crate) fn should_replace_file(&mut self, file_name: &str) -> bool { pub(crate) fn should_replace_file(&mut self, file_name: String) -> bool {
self.mount_radio_replace(file_name); self.mount_radio_replace(&file_name);
// Wait for answer // Wait for answer
trace!("Asking user whether he wants to replace file {}", file_name); trace!("Asking user whether he wants to replace file {}", file_name);
if self.wait_for_pending_msg(&[ if self.wait_for_pending_msg(&[
@@ -201,8 +199,8 @@ impl FileTransferActivity {
} }
/// Set pending transfer for many files into storage and mount radio /// Set pending transfer for many files into storage and mount radio
pub(crate) fn should_replace_files(&mut self, files: Vec<&Entry>) -> bool { pub(crate) fn should_replace_files(&mut self, files: Vec<&File>) -> bool {
let file_names: Vec<&str> = files.iter().map(|x| x.name()).collect(); let file_names: Vec<String> = files.iter().map(|x| x.name()).collect();
self.mount_radio_replace_many(file_names.as_slice()); self.mount_radio_replace_many(file_names.as_slice());
// Wait for answer // Wait for answer
trace!( trace!(
@@ -225,14 +223,14 @@ impl FileTransferActivity {
} }
/// Get file to check for path /// 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 { match alt {
Some(s) => PathBuf::from(s), Some(s) => PathBuf::from(s),
None => PathBuf::from(e.name()), 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(); let mut p = wrkdir.to_path_buf();
p.push(e.name()); p.push(e.name());
p p

View File

@@ -26,9 +26,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{Entry, FileTransferActivity}; use super::{File, FileTransferActivity};
use remotefs::fs::{File, Metadata};
enum SubmitAction { enum SubmitAction {
ChangeDir, ChangeDir,
@@ -38,73 +36,59 @@ enum SubmitAction {
impl FileTransferActivity { impl FileTransferActivity {
/// Decides which action to perform on submit for local explorer /// Decides which action to perform on submit for local explorer
/// Return true whether the directory changed /// Return true whether the directory changed
pub(crate) fn action_submit_local(&mut self, entry: Entry) { pub(crate) fn action_submit_local(&mut self, entry: File) {
let (action, entry) = match &entry { let (action, entry) = if entry.is_dir() {
Entry::Directory(_) => (SubmitAction::ChangeDir, entry), (SubmitAction::ChangeDir, entry)
Entry::File(File { } else if entry.metadata().symlink.is_some() {
path, // Stat file
metadata: let symlink = entry.metadata().symlink.as_ref().unwrap();
Metadata { let stat_file = match self.host.stat(symlink.as_path()) {
symlink: Some(symlink), Ok(e) => e,
.. Err(err) => {
}, warn!(
.. "Could not stat file pointed by {} ({}): {}",
}) => { entry.path().display(),
// Stat file symlink.display(),
let stat_file = match self.host.stat(symlink.as_path()) { err
Ok(e) => e, );
Err(err) => { entry
warn!( }
"Could not stat file pointed by {} ({}): {}", };
path.display(), (SubmitAction::ChangeDir, stat_file)
symlink.display(), } else {
err (SubmitAction::None, entry)
);
entry
}
};
(SubmitAction::ChangeDir, stat_file)
}
Entry::File(_) => (SubmitAction::None, entry),
}; };
if let (SubmitAction::ChangeDir, Entry::Directory(dir)) = (action, entry) { if let (SubmitAction::ChangeDir, entry) = (action, entry) {
self.action_enter_local_dir(dir) self.action_enter_local_dir(entry)
} }
} }
/// Decides which action to perform on submit for remote explorer /// Decides which action to perform on submit for remote explorer
/// Return true whether the directory changed /// Return true whether the directory changed
pub(crate) fn action_submit_remote(&mut self, entry: Entry) { pub(crate) fn action_submit_remote(&mut self, entry: File) {
let (action, entry) = match &entry { let (action, entry) = if entry.is_dir() {
Entry::Directory(_) => (SubmitAction::ChangeDir, entry), (SubmitAction::ChangeDir, entry)
Entry::File(File { } else if entry.metadata().symlink.is_some() {
path, // Stat file
metadata: let symlink = entry.metadata().symlink.as_ref().unwrap();
Metadata { let stat_file = match self.client.stat(symlink.as_path()) {
symlink: Some(symlink), Ok(e) => e,
.. Err(err) => {
}, warn!(
.. "Could not stat file pointed by {} ({}): {}",
}) => { entry.path().display(),
// Stat file symlink.display(),
let stat_file = match self.client.stat(symlink.as_path()) { err
Ok(e) => e, );
Err(err) => { entry
warn!( }
"Could not stat file pointed by {} ({}): {}", };
path.display(), (SubmitAction::ChangeDir, stat_file)
symlink.display(), } else {
err (SubmitAction::None, entry)
);
entry
}
};
(SubmitAction::ChangeDir, stat_file)
}
Entry::File(_) => (SubmitAction::None, entry),
}; };
if let (SubmitAction::ChangeDir, Entry::Directory(dir)) = (action, entry) { if let (SubmitAction::ChangeDir, entry) = (action, entry) {
self.action_enter_remote_dir(dir) self.action_enter_remote_dir(entry)
} }
} }
} }

View File

@@ -26,7 +26,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
// locals // locals
use super::{FileTransferActivity, LogLevel, SelectedEntry}; use super::{FileTransferActivity, LogLevel, SelectedFile};
use std::path::PathBuf; use std::path::PathBuf;
@@ -34,7 +34,7 @@ impl FileTransferActivity {
/// Create symlink on localhost /// Create symlink on localhost
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub(crate) fn action_local_symlink(&mut self, name: String) { 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 match self
.host .host
.symlink(PathBuf::from(name.as_str()).as_path(), entry.path()) .symlink(PathBuf::from(name.as_str()).as_path(), entry.path())
@@ -66,7 +66,7 @@ impl FileTransferActivity {
/// Copy file on remote /// Copy file on remote
pub(crate) fn action_remote_symlink(&mut self, name: String) { 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 match self
.client .client
.symlink(PathBuf::from(name.as_str()).as_path(), entry.path()) .symlink(PathBuf::from(name.as_str()).as_path(), entry.path())

View File

@@ -31,7 +31,8 @@ use crate::explorer::FileSorting;
use crate::utils::fmt::fmt_time; use crate::utils::fmt::fmt_time;
use bytesize::ByteSize; 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 tui_realm_stdlib::{Input, List, Paragraph, ProgressBar, Radio, Span};
use tuirealm::command::{Cmd, CmdResult, Direction, Position}; use tuirealm::command::{Cmd, CmdResult, Direction, Position};
@@ -399,7 +400,7 @@ pub struct FileInfoPopup {
} }
impl FileInfoPopup { impl FileInfoPopup {
pub fn new(file: &Entry) -> Self { pub fn new(file: &File) -> Self {
let mut texts: TableBuilder = TableBuilder::default(); let mut texts: TableBuilder = TableBuilder::default();
// Abs path // Abs path
let real_path = file.metadata().symlink.as_deref(); let real_path = file.metadata().symlink.as_deref();
@@ -422,9 +423,18 @@ impl FileInfoPopup {
.add_row() .add_row()
.add_col(TextSpan::from("Size: ")) .add_col(TextSpan::from("Size: "))
.add_col(TextSpan::new(format!("{} ({})", bsize, size).as_str()).fg(Color::Cyan)); .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 atime: String = fmt_time(
let ctime: String = fmt_time(file.metadata().ctime, "%b %d %Y %H:%M:%S"); file.metadata().accessed.unwrap_or(UNIX_EPOCH),
let mtime: String = fmt_time(file.metadata().mtime, "%b %d %Y %H:%M:%S"); "%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 texts
.add_row() .add_row()
.add_col(TextSpan::from("Creation time: ")) .add_col(TextSpan::from("Creation time: "))
@@ -1373,7 +1383,7 @@ pub struct ReplacingFilesListPopup {
} }
impl ReplacingFilesListPopup { impl ReplacingFilesListPopup {
pub fn new(files: &[&str], color: Color) -> Self { pub fn new(files: &[String], color: Color) -> Self {
Self { Self {
component: List::default() component: List::default()
.borders( .borders(

View File

@@ -28,7 +28,7 @@
use crate::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSorting, GroupDirs}; use crate::explorer::{builder::FileExplorerBuilder, FileExplorer, FileSorting, GroupDirs};
use crate::system::config_client::ConfigClient; use crate::system::config_client::ConfigClient;
use remotefs::Entry; use remotefs::File;
use std::path::Path; use std::path::Path;
/// File explorer tab /// File explorer tab
@@ -92,7 +92,7 @@ impl Browser {
self.found.as_mut().map(|x| &mut x.1) self.found.as_mut().map(|x| &mut x.1)
} }
pub fn set_found(&mut self, tab: FoundExplorerTab, files: Vec<Entry>, wrkdir: &Path) { pub fn set_found(&mut self, tab: FoundExplorerTab, files: Vec<File>, wrkdir: &Path) {
let mut explorer = Self::build_found_explorer(wrkdir); let mut explorer = Self::build_found_explorer(wrkdir);
explorer.set_files(files); explorer.set_files(files);
self.found = Some((tab, explorer)); self.found = Some((tab, explorer));

View File

@@ -191,7 +191,8 @@ impl FileTransferActivity {
TransferPayload::File(file) => { TransferPayload::File(file) => {
format!( format!(
"File \"{}\" has been successfully transferred ({})", "File \"{}\" has been successfully transferred ({})",
file.name, transfer_stats file.name(),
transfer_stats
) )
} }
TransferPayload::Any(entry) => { TransferPayload::Any(entry) => {

View File

@@ -32,7 +32,7 @@ use crate::utils::fmt::fmt_millis;
// Ext // Ext
use bytesize::ByteSize; use bytesize::ByteSize;
use remotefs::fs::{Entry, File, UnixPex, Welcome}; use remotefs::fs::{File, ReadStream, UnixPex, Welcome, WriteStream};
use remotefs::{RemoteError, RemoteErrorType}; use remotefs::{RemoteError, RemoteErrorType};
use std::fs::File as StdFile; use std::fs::File as StdFile;
use std::io::{Read, Seek, Write}; use std::io::{Read, Seek, Write};
@@ -59,13 +59,13 @@ enum TransferErrorReason {
/// Represents the entity to send or receive during a transfer. /// Represents the entity to send or receive during a transfer.
/// - File: describes an individual `File` to send /// - File: describes an individual `File` to send
/// - Any: Can be any kind of `Entry`, but just one /// - Any: Can be any kind of `File`, but just one
/// - Many: a list of `Entry` /// - Many: a list of `File`
#[derive(Debug)] #[derive(Debug)]
pub(super) enum TransferPayload { pub(super) enum TransferPayload {
File(File), File(File),
Any(Entry), Any(File),
Many(Vec<Entry>), Many(Vec<File>),
} }
impl FileTransferActivity { impl FileTransferActivity {
@@ -224,7 +224,7 @@ impl FileTransferActivity {
// Mount progress bar // Mount progress bar
self.mount_progress_bar(format!("Uploading {}", file.path.display())); self.mount_progress_bar(format!("Uploading {}", file.path.display()));
// Get remote path // 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 mut remote_path: PathBuf = PathBuf::from(curr_remote_path);
let remote_file_name: PathBuf = match dst_name { let remote_file_name: PathBuf = match dst_name {
Some(s) => PathBuf::from(s.as_str()), Some(s) => PathBuf::from(s.as_str()),
@@ -242,7 +242,7 @@ impl FileTransferActivity {
/// Send a `TransferPayload` of type `Any` /// Send a `TransferPayload` of type `Any`
fn filetransfer_send_any( fn filetransfer_send_any(
&mut self, &mut self,
entry: &Entry, entry: &File,
curr_remote_path: &Path, curr_remote_path: &Path,
dst_name: Option<String>, dst_name: Option<String>,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -263,7 +263,7 @@ impl FileTransferActivity {
/// Send many entries to remote /// Send many entries to remote
fn filetransfer_send_many( fn filetransfer_send_many(
&mut self, &mut self,
entries: &[Entry], entries: &[File],
curr_remote_path: &Path, curr_remote_path: &Path,
) -> Result<(), String> { ) -> Result<(), String> {
// Reset states // Reset states
@@ -289,15 +289,12 @@ impl FileTransferActivity {
fn filetransfer_send_recurse( fn filetransfer_send_recurse(
&mut self, &mut self,
entry: &Entry, entry: &File,
curr_remote_path: &Path, curr_remote_path: &Path,
dst_name: Option<String>, dst_name: Option<String>,
) -> Result<(), String> { ) -> Result<(), String> {
// Write popup // Write popup
let file_name: String = match entry { let file_name = entry.name();
Entry::Directory(dir) => dir.name.clone(),
Entry::File(file) => file.name.clone(),
};
// Get remote path // Get remote path
let mut remote_path: PathBuf = PathBuf::from(curr_remote_path); let mut remote_path: PathBuf = PathBuf::from(curr_remote_path);
let remote_file_name: PathBuf = match dst_name { let remote_file_name: PathBuf = match dst_name {
@@ -306,107 +303,104 @@ impl FileTransferActivity {
}; };
remote_path.push(remote_file_name); remote_path.push(remote_file_name);
// Match entry // Match entry
let result: Result<(), String> = match entry { let result: Result<(), String> = if entry.is_dir() {
Entry::File(file) => { // Create directory on remote first
match self.filetransfer_send_one(file, remote_path.as_path(), file_name) { match self
Err(err) => { .client
// If transfer was abrupted or there was an IO error on remote, remove file .create_dir(remote_path.as_path(), UnixPex::from(0o755))
if matches!( {
err, Ok(_) => {
TransferErrorReason::Abrupted | TransferErrorReason::RemoteIoError(_) self.log(
) { LogLevel::Info,
// Stat file on remote and remove it if exists format!("Created directory \"{}\"", remote_path.display()),
match self.client.stat(remote_path.as_path()) { );
Err(err) => self.log( }
LogLevel::Error, Err(err) if err.kind == RemoteErrorType::DirectoryAlreadyExists => {
format!( self.log(
"Could not remove created file {}: {}", LogLevel::Info,
remote_path.display(), format!(
err "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()) { Ok(entry) => {
self.log( if let Err(err) = self.client.remove_file(entry.path()) {
LogLevel::Error, self.log(
format!( LogLevel::Error,
"Could not remove created file {}: {}", format!(
remote_path.display(), "Could not remove created file {}: {}",
err 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 // Scan dir on remote
@@ -458,7 +452,7 @@ impl FileTransferActivity {
remote: &Path, remote: &Path,
file_name: String, file_name: String,
mut reader: StdFile, mut reader: StdFile,
mut writer: Box<dyn Write>, mut writer: WriteStream,
) -> Result<(), TransferErrorReason> { ) -> Result<(), TransferErrorReason> {
// Write file // Write file
let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize; 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 /// If entry is a directory, this applies to directory only
fn filetransfer_recv_any( fn filetransfer_recv_any(
&mut self, &mut self,
entry: &Entry, entry: &File,
local_path: &Path, local_path: &Path,
dst_name: Option<String>, dst_name: Option<String>,
) -> Result<(), String> { ) -> Result<(), String> {
@@ -660,7 +654,7 @@ impl FileTransferActivity {
// Mount progress bar // Mount progress bar
self.mount_progress_bar(format!("Downloading {}", entry.path.display())); self.mount_progress_bar(format!("Downloading {}", entry.path.display()));
// Receive // 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 // Umount progress bar
self.umount_progress_bar(); self.umount_progress_bar();
// Return result // Return result
@@ -670,7 +664,7 @@ impl FileTransferActivity {
/// Send many entries to remote /// Send many entries to remote
fn filetransfer_recv_many( fn filetransfer_recv_many(
&mut self, &mut self,
entries: &[Entry], entries: &[File],
curr_remote_path: &Path, curr_remote_path: &Path,
) -> Result<(), String> { ) -> Result<(), String> {
// Reset states // Reset states
@@ -696,142 +690,132 @@ impl FileTransferActivity {
fn filetransfer_recv_recurse( fn filetransfer_recv_recurse(
&mut self, &mut self,
entry: &Entry, entry: &File,
local_path: &Path, local_path: &Path,
dst_name: Option<String>, dst_name: Option<String>,
) -> Result<(), String> { ) -> Result<(), String> {
// Write popup // Write popup
let file_name: String = match entry { let file_name = entry.name();
Entry::Directory(dir) => dir.name.clone(),
Entry::File(file) => file.name.clone(),
};
// Match entry // Match entry
let result: Result<(), String> = match entry { let result: Result<(), String> = if entry.is_dir() {
Entry::File(file) => { // Get dir name
// Get local file let mut local_dir_path: PathBuf = PathBuf::from(local_path);
let mut local_file_path: PathBuf = PathBuf::from(local_path); match dst_name {
let local_file_name: String = match dst_name { Some(name) => local_dir_path.push(name),
Some(n) => n, None => local_dir_path.push(entry.name()),
None => file.name.clone(), }
}; // Create directory on local
local_file_path.push(local_file_name.as_str()); match self.host.mkdir_ex(local_dir_path.as_path(), true) {
// Download file Ok(_) => {
if let Err(err) = // Apply file mode to directory
self.filetransfer_recv_one(local_file_path.as_path(), file, file_name) #[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))]
{ if let Some(mode) = entry.metadata().mode {
// If transfer was abrupted or there was an IO error on remote, remove file if let Err(err) = self.host.chmod(local_dir_path.as_path(), mode) {
if matches!( self.log(
err,
TransferErrorReason::Abrupted | TransferErrorReason::LocalIoError(_)
) {
// Stat file
match self.host.stat(local_file_path.as_path()) {
Err(err) => self.log(
LogLevel::Error, LogLevel::Error,
format!( format!(
"Could not remove created file {}: {}", "Could not apply file mode {:o} to \"{}\": {}",
local_file_path.display(), u32::from(mode),
local_dir_path.display(),
err 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()) Err(err.to_string())
} else {
Ok(())
} }
} }
Entry::Directory(dir) => { } else {
// Get dir name // Get local file
let mut local_dir_path: PathBuf = PathBuf::from(local_path); let mut local_file_path: PathBuf = PathBuf::from(local_path);
match dst_name { let local_file_name: String = match dst_name {
Some(name) => local_dir_path.push(name), Some(n) => n,
None => local_dir_path.push(dir.name.as_str()), None => entry.name(),
} };
// Create directory on local local_file_path.push(local_file_name.as_str());
match self.host.mkdir_ex(local_dir_path.as_path(), true) { // Download file
Ok(_) => { if let Err(err) =
// Apply file mode to directory self.filetransfer_recv_one(local_file_path.as_path(), entry, file_name)
#[cfg(any( {
target_family = "unix", // If transfer was abrupted or there was an IO error on remote, remove file
target_os = "macos", if matches!(
target_os = "linux" err,
))] TransferErrorReason::Abrupted | TransferErrorReason::LocalIoError(_)
if let Some(mode) = dir.metadata.mode { ) {
if let Err(err) = self.host.chmod(local_dir_path.as_path(), mode) { // 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( self.log(
LogLevel::Error, LogLevel::Error,
format!( format!(
"Could not apply file mode {:o} to \"{}\": {}", "Could not remove created file {}: {}",
u32::from(mode), local_file_path.display(),
local_dir_path.display(),
err 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 // 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( fn filetransfer_recv_one_with_stream(
&mut self, &mut self,
local: &Path, local: &Path,
remote: &File, remote: &File,
file_name: String, file_name: String,
mut reader: Box<dyn Read>, mut reader: ReadStream,
mut writer: StdFile, mut writer: StdFile,
) -> Result<(), TransferErrorReason> { ) -> Result<(), TransferErrorReason> {
let mut total_bytes_written: usize = 0; let mut total_bytes_written: usize = 0;
@@ -979,7 +963,7 @@ impl FileTransferActivity {
Ok(()) Ok(())
} }
/// Receive an `Entry` from remote without using stream /// Receive an `File` from remote without using stream
fn filetransfer_recv_one_wno_stream( fn filetransfer_recv_one_wno_stream(
&mut self, &mut self,
local: &Path, local: &Path,
@@ -1098,7 +1082,7 @@ impl FileTransferActivity {
let tmpfile: PathBuf = match self.cache.as_ref() { let tmpfile: PathBuf = match self.cache.as_ref() {
Some(cache) => { Some(cache) => {
let mut p: PathBuf = cache.path().to_path_buf(); let mut p: PathBuf = cache.path().to_path_buf();
p.push(file.name.as_str()); p.push(file.name());
p p
} }
None => { None => {
@@ -1111,7 +1095,7 @@ impl FileTransferActivity {
match self.filetransfer_recv( match self.filetransfer_recv(
TransferPayload::File(file.clone()), TransferPayload::File(file.clone()),
tmpfile.as_path(), tmpfile.as_path(),
Some(file.name.clone()), Some(file.name()),
) { ) {
Err(err) => Err(format!( Err(err) => Err(format!(
"Could not download {} to temporary file: {}", "Could not download {} to temporary file: {}",
@@ -1125,48 +1109,54 @@ impl FileTransferActivity {
// -- transfer sizes // -- transfer sizes
/// Get total size of transfer for localhost /// Get total size of transfer for localhost
fn get_total_transfer_size_local(&mut self, entry: &Entry) -> usize { fn get_total_transfer_size_local(&mut self, entry: &File) -> usize {
match entry { if entry.is_dir() {
Entry::File(file) => file.metadata.size as usize, // List dir
Entry::Directory(dir) => { match self.host.scan_dir(entry.path()) {
// List dir Ok(files) => files
match self.host.scan_dir(dir.path.as_path()) { .iter()
Ok(files) => files .map(|x| self.get_total_transfer_size_local(x))
.iter() .sum(),
.map(|x| self.get_total_transfer_size_local(x)) Err(err) => {
.sum(), self.log(
Err(err) => { LogLevel::Error,
self.log( format!(
LogLevel::Error, "Could not list directory {}: {}",
format!("Could not list directory {}: {}", dir.path.display(), err), entry.path().display(),
); err
0 ),
} );
0
} }
} }
} else {
entry.metadata.size as usize
} }
} }
/// Get total size of transfer for remote host /// Get total size of transfer for remote host
fn get_total_transfer_size_remote(&mut self, entry: &Entry) -> usize { fn get_total_transfer_size_remote(&mut self, entry: &File) -> usize {
match entry { if entry.is_dir() {
Entry::File(file) => file.metadata.size as usize, // List directory
Entry::Directory(dir) => { match self.client.list_dir(entry.path()) {
// List directory Ok(files) => files
match self.client.list_dir(dir.path.as_path()) { .iter()
Ok(files) => files .map(|x| self.get_total_transfer_size_remote(x))
.iter() .sum(),
.map(|x| self.get_total_transfer_size_remote(x)) Err(err) => {
.sum(), self.log(
Err(err) => { LogLevel::Error,
self.log( format!(
LogLevel::Error, "Could not list directory {}: {}",
format!("Could not list directory {}: {}", dir.path.display(), err), entry.path().display(),
); err
0 ),
} );
0
} }
} }
} else {
entry.metadata.size as usize
} }
} }

View File

@@ -27,12 +27,12 @@
*/ */
// locals // locals
use super::{ use super::{
actions::SelectedEntry, actions::SelectedFile,
browser::{FileExplorerTab, FoundExplorerTab}, browser::{FileExplorerTab, FoundExplorerTab},
ExitReason, FileTransferActivity, Id, Msg, TransferMsg, TransferOpts, UiMsg, ExitReason, FileTransferActivity, Id, Msg, TransferMsg, TransferOpts, UiMsg,
}; };
// externals // externals
use remotefs::fs::Entry; use remotefs::fs::File;
use tuirealm::{ use tuirealm::{
props::{AttrValue, Attribute}, props::{AttrValue, Attribute},
State, StateValue, Update, State, StateValue, Update,
@@ -121,7 +121,7 @@ impl FileTransferActivity {
} }
} }
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Local => { 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); self.action_submit_local(entry);
// Update file list if sync // Update file list if sync
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
@@ -131,7 +131,7 @@ impl FileTransferActivity {
} }
} }
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Remote => { 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); self.action_submit_remote(entry);
// Update file list if sync // Update file list if sync
if self.browser.sync_browsing && self.browser.found().is_none() { if self.browser.sync_browsing && self.browser.found().is_none() {
@@ -296,7 +296,7 @@ impl FileTransferActivity {
// Mount wait // Mount wait
self.mount_blocking_wait(format!(r#"Searching for "{}"…"#, search).as_str()); self.mount_blocking_wait(format!(r#"Searching for "{}"…"#, search).as_str());
// Find // Find
let res: Result<Vec<Entry>, String> = match self.browser.tab() { let res: Result<Vec<File>, String> = match self.browser.tab() {
FileExplorerTab::Local => self.action_local_find(search.clone()), FileExplorerTab::Local => self.action_local_find(search.clone()),
FileExplorerTab::Remote => self.action_remote_find(search.clone()), FileExplorerTab::Remote => self.action_remote_find(search.clone()),
_ => panic!("Trying to search for files, while already in a find result"), _ => panic!("Trying to search for files, while already in a find result"),
@@ -449,17 +449,17 @@ impl FileTransferActivity {
UiMsg::ShowDisconnectPopup => self.mount_disconnect(), UiMsg::ShowDisconnectPopup => self.mount_disconnect(),
UiMsg::ShowExecPopup => self.mount_exec(), UiMsg::ShowExecPopup => self.mount_exec(),
UiMsg::ShowFileInfoPopup if self.browser.tab() == FileExplorerTab::Local => { 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); self.mount_file_info(&file);
} }
} }
UiMsg::ShowFileInfoPopup if self.browser.tab() == FileExplorerTab::Remote => { 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); self.mount_file_info(&file);
} }
} }
UiMsg::ShowFileInfoPopup => { 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); self.mount_file_info(&file);
} }
} }

View File

@@ -33,7 +33,7 @@ use super::{
use crate::explorer::FileSorting; use crate::explorer::FileSorting;
use crate::utils::ui::draw_area_in; use crate::utils::ui::draw_area_in;
// Ext // Ext
use remotefs::fs::Entry; use remotefs::fs::File;
use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::tui::layout::{Constraint, Direction, Layout}; use tuirealm::tui::layout::{Constraint, Direction, Layout};
use tuirealm::tui::widgets::Clear; use tuirealm::tui::widgets::Clear;
@@ -719,7 +719,7 @@ impl FileTransferActivity {
assert!(self.app.active(&Id::ReplacePopup).is_ok()); 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; let warn_color = self.theme().misc_warn_dialog;
assert!(self assert!(self
.app .app
@@ -750,7 +750,7 @@ impl FileTransferActivity {
let _ = self.app.umount(&Id::ReplacingFilesListPopup); // NOTE: replace anyway 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 assert!(self
.app .app
.remount( .remount(

View File

@@ -25,7 +25,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
use remotefs::fs::{Directory, Entry, File, Metadata}; use remotefs::fs::{File, FileType, Metadata};
// ext // ext
use std::fs::File as StdFile; use std::fs::File as StdFile;
use std::io::Write; use std::io::Write;
@@ -37,14 +37,7 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) {
let tmpfile = create_sample_file(); let tmpfile = create_sample_file();
( (
File { File {
name: tmpfile
.path()
.file_name()
.unwrap()
.to_string_lossy()
.to_string(),
path: tmpfile.path().to_path_buf(), path: tmpfile.path().to_path_buf(),
extension: None,
metadata: Metadata::default(), metadata: Metadata::default(),
}, },
tmpfile, tmpfile,
@@ -85,20 +78,15 @@ pub fn make_dir_at(dir: &Path, dirname: &str) -> std::io::Result<()> {
std::fs::create_dir(p.as_path()) std::fs::create_dir(p.as_path())
} }
/// Create a Entry at specified path /// Create a File at specified path
pub fn make_fsentry<P: AsRef<Path>>(path: P, is_dir: bool) -> Entry { pub fn make_fsentry<P: AsRef<Path>>(path: P, is_dir: bool) -> File {
let path: PathBuf = path.as_ref().to_path_buf(); let path: PathBuf = path.as_ref().to_path_buf();
match is_dir { File {
true => Entry::Directory(Directory { path,
name: path.file_name().unwrap().to_string_lossy().to_string(), metadata: Metadata::default().file_type(if is_dir {
path, FileType::Directory
metadata: Metadata::default(), } else {
}), FileType::File
false => Entry::File(File {
name: path.file_name().unwrap().to_string_lossy().to_string(),
path,
extension: None,
metadata: Metadata::default(),
}), }),
} }
} }
@@ -127,15 +115,13 @@ mod test {
fn test_utils_test_helpers_make_fsentry() { fn test_utils_test_helpers_make_fsentry() {
assert_eq!( assert_eq!(
make_fsentry(PathBuf::from("/tmp/omar.txt"), false) make_fsentry(PathBuf::from("/tmp/omar.txt"), false)
.unwrap_file() .name()
.name
.as_str(), .as_str(),
"omar.txt" "omar.txt"
); );
assert_eq!( assert_eq!(
make_fsentry(PathBuf::from("/tmp/cards"), true) make_fsentry(PathBuf::from("/tmp/cards"), true)
.unwrap_dir() .name()
.name
.as_str(), .as_str(),
"cards" "cards"
); );