mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
remotefs 0.2.0
This commit is contained in:
committed by
Christian Visintin
parent
edd0842273
commit
ec4daf8e25
@@ -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
57
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
11
Cargo.toml
11
Cargo.toml
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
395
src/host/mod.rs
395
src/host/mod.rs
@@ -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");
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|
||||||
|
|||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 \"{}\": {}",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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"
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user