Merge pull request #51 from veeso/issue-38-transfer-size-is-wrong-when-transferring-selected-files

Fixed transfer size when sending multiple entries
This commit is contained in:
Christian Visintin 2021-06-18 14:30:12 +02:00 committed by GitHub
commit 76f1f1dcf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 299 additions and 105 deletions

View file

@ -39,6 +39,7 @@ Released on FIXME: ??
- Fixed [Issue 44](https://github.com/veeso/termscp/issues/44): Could not move files to other paths in FTP
- Fixed [Issue 43](https://github.com/veeso/termscp/issues/43): Could not remove non-empty directories in FTP
- Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens
- Fixed [Issue 38](https://github.com/veeso/termscp/issues/38): Transfer size was wrong when transferring "selected" files (with mark)
- Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files
- Dependencies:
- Updated `textwrap` to `0.14.0`

View file

@ -227,7 +227,6 @@ impl FsEntry {
}
}
#[cfg(test)]
/// ### unwrap_file
///
/// Unwrap FsEntry as FsFile

View file

@ -27,8 +27,9 @@
*/
extern crate tempfile;
// locals
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
use crate::filetransfer::FileTransferErrorType;
use crate::fs::FsFile;
use std::path::{Path, PathBuf};
impl FileTransferActivity {
@ -66,7 +67,7 @@ impl FileTransferActivity {
match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => {
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());
// Reload entries
self.reload_remote_dir();
}
@ -74,7 +75,7 @@ impl FileTransferActivity {
// Try to copy each file to Input/{FILE_NAME}
let base_path: PathBuf = PathBuf::from(input);
// Iter files
for entry in entries.iter() {
for entry in entries.into_iter() {
let mut dest_path: PathBuf = base_path.clone();
dest_path.push(entry.get_name());
self.remote_copy_file(entry, dest_path.as_path());
@ -110,8 +111,8 @@ impl FileTransferActivity {
}
}
fn remote_copy_file(&mut self, entry: &FsEntry, dest: &Path) {
match self.client.as_mut().copy(entry, dest) {
fn remote_copy_file(&mut self, entry: FsEntry, dest: &Path) {
match self.client.as_mut().copy(&entry, dest) {
Ok(_) => {
self.log(
LogLevel::Info,
@ -143,7 +144,7 @@ impl FileTransferActivity {
/// ### tricky_copy
///
/// Tricky copy will be used whenever copy command is not available on remote host
fn tricky_copy(&mut self, entry: &FsEntry, dest: &Path) {
fn tricky_copy(&mut self, entry: FsEntry, dest: &Path) {
// match entry
match entry {
FsEntry::File(entry) => {
@ -159,8 +160,10 @@ impl FileTransferActivity {
}
};
// Download file
let name = entry.name.clone();
let entry_path = entry.abs_path.clone();
if let Err(err) =
self.filetransfer_recv_one(entry, tmpfile.path(), entry.name.clone())
self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name))
{
self.log_and_alert(
LogLevel::Error,
@ -169,8 +172,8 @@ impl FileTransferActivity {
return;
}
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
Ok(e) => e,
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) {
Ok(e) => e.unwrap_file(),
Err(err) => {
self.log_and_alert(
LogLevel::Error,
@ -183,14 +186,10 @@ impl FileTransferActivity {
return;
}
};
let tmpfile_entry = match &tmpfile_entry {
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
FsEntry::File(f) => f,
};
// Upload file to destination
let wrkdir = self.remote().wrkdir.clone();
if let Err(err) = self.filetransfer_send_one(
tmpfile_entry,
if let Err(err) = self.filetransfer_send(
TransferPayload::File(tmpfile_entry),
wrkdir.as_path(),
Some(String::from(dest.to_string_lossy())),
) {
@ -198,7 +197,7 @@ impl FileTransferActivity {
LogLevel::Error,
format!(
"Copy failed: could not write file {}: {}",
entry.abs_path.display(),
entry_path.display(),
err
),
);
@ -216,11 +215,19 @@ impl FileTransferActivity {
return;
}
};
// Download file
self.filetransfer_recv(entry, tempdir.path(), None);
// Get path of dest
let mut tempdir_path: PathBuf = tempdir.path().to_path_buf();
tempdir_path.push(entry.get_name());
// Download file
if let Err(err) =
self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None)
{
self.log_and_alert(
LogLevel::Error,
format!("Copy failed: failed to download file: {}", err),
);
return;
}
// Stat dir
let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) {
Ok(e) => e,
@ -238,11 +245,17 @@ impl FileTransferActivity {
};
// Upload to destination
let wrkdir: PathBuf = self.remote().wrkdir.clone();
self.filetransfer_send(
&tempdir_entry,
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;
}
}
}
}

View file

@ -26,7 +26,7 @@
* SOFTWARE.
*/
// locals
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
use crate::fs::FsFile;
// ext
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
@ -67,15 +67,15 @@ impl FileTransferActivity {
SelectedEntry::None => vec![],
};
// Edit all entries
for entry in entries.iter() {
for entry in entries.into_iter() {
// Check if file
if let FsEntry::File(file) = entry {
self.log(
LogLevel::Info,
format!("Opening file \"{}\"...", entry.get_abs_path().display()),
format!("Opening file \"{}\"...", file.abs_path.display()),
);
// Edit file
if let Err(err) = self.edit_remote_file(&file) {
if let Err(err) = self.edit_remote_file(file) {
self.log_and_alert(LogLevel::Error, err);
}
}
@ -141,7 +141,7 @@ impl FileTransferActivity {
/// ### edit_remote_file
///
/// Edit file on remote host
fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> {
fn edit_remote_file(&mut self, file: FsFile) -> Result<(), String> {
// Create temp file
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
Ok(f) => f,
@ -150,8 +150,14 @@ impl FileTransferActivity {
}
};
// Download file
if let Err(err) = self.filetransfer_recv_one(file, tmpfile.path(), file.name.clone()) {
return Err(format!("Could not open file {}: {}", file.name, err));
let file_name = file.name.clone();
let file_path = file.abs_path.clone();
if let Err(err) = self.filetransfer_recv(
TransferPayload::File(file),
tmpfile.path(),
Some(file_name.clone()),
) {
return Err(format!("Could not open file {}: {}", file_name, err));
}
// Get current file modification time
let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) {
@ -186,12 +192,12 @@ impl FileTransferActivity {
LogLevel::Info,
format!(
"File \"{}\" has changed; writing changes to remote",
file.abs_path.display()
file_path.display()
),
);
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
Ok(e) => e,
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) {
Ok(e) => e.unwrap_file(),
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
@ -200,21 +206,16 @@ impl FileTransferActivity {
))
}
};
// Write file
let tmpfile_entry: &FsFile = match &tmpfile_entry {
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
FsEntry::File(f) => f,
};
// Send file
let wrkdir = self.remote().wrkdir.clone();
if let Err(err) = self.filetransfer_send_one(
tmpfile_entry,
if let Err(err) = self.filetransfer_send(
TransferPayload::File(tmpfile_entry),
wrkdir.as_path(),
Some(file.name.clone()),
Some(file_name),
) {
return Err(format!(
"Could not write file {}: {}",
file.abs_path.display(),
file_path.display(),
err
));
}
@ -222,7 +223,7 @@ impl FileTransferActivity {
false => {
self.log(
LogLevel::Info,
format!("File \"{}\" hasn't changed", file.abs_path.display()),
format!("File \"{}\" hasn't changed", file_path.display()),
);
}
}

View file

@ -27,7 +27,7 @@
*/
// locals
use super::super::browser::FileExplorerTab;
use super::{FileTransferActivity, FsEntry, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
use std::path::PathBuf;
@ -77,10 +77,30 @@ impl FileTransferActivity {
match self.get_found_selected_entries() {
SelectedEntry::One(entry) => match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_send(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_recv(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
},
SelectedEntry::Many(entries) => {
@ -90,21 +110,34 @@ impl FileTransferActivity {
dest_path.push(save_as);
}
// Iter files
for entry in entries.iter() {
match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
self.filetransfer_send(
&entry.get_realfile(),
dest_path.as_path(),
None,
);
let entries = entries.iter().map(|x| x.get_realfile()).collect();
match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
if let Err(err) = self.filetransfer_send(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
self.filetransfer_recv(
&entry.get_realfile(),
dest_path.as_path(),
None,
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
if let Err(err) = self.filetransfer_recv(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}

View file

@ -25,7 +25,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel};
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel, TransferPayload};
use tuirealm::{Payload, Value};
// actions
@ -78,8 +78,7 @@ impl FileTransferActivity {
let files: Vec<&FsEntry> = files
.iter()
.map(|x| self.local().get(*x)) // Usize to Option<FsEntry>
.filter(|x| x.is_some()) // Get only some values
.map(|x| x.unwrap()) // Option to FsEntry
.flatten()
.collect();
SelectedEntry::from(files)
}
@ -97,8 +96,7 @@ impl FileTransferActivity {
let files: Vec<&FsEntry> = files
.iter()
.map(|x| self.remote().get(*x)) // Usize to Option<FsEntry>
.filter(|x| x.is_some()) // Get only some values
.map(|x| x.unwrap()) // Option to FsEntry
.flatten()
.collect();
SelectedEntry::from(files)
}
@ -118,8 +116,7 @@ impl FileTransferActivity {
let files: Vec<&FsEntry> = files
.iter()
.map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option<FsEntry>
.filter(|x| x.is_some()) // Get only some values
.map(|x| x.unwrap()) // Option to FsEntry
.flatten()
.collect();
SelectedEntry::from(files)
}

View file

@ -26,7 +26,7 @@
* SOFTWARE.
*/
// locals
use super::{FileTransferActivity, SelectedEntry};
use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload};
use std::path::PathBuf;
impl FileTransferActivity {
@ -50,7 +50,19 @@ impl FileTransferActivity {
let wrkdir: PathBuf = self.remote().wrkdir.clone();
match self.get_local_selected_entries() {
SelectedEntry::One(entry) => {
self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_send(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
SelectedEntry::Many(entries) => {
// In case of selection: save multiple files in wrkdir/input
@ -59,8 +71,19 @@ impl FileTransferActivity {
dest_path.push(save_as);
}
// Iter files
for entry in entries.iter() {
self.filetransfer_send(&entry.get_realfile(), dest_path.as_path(), None);
let entries = entries.iter().map(|x| x.get_realfile()).collect();
if let Err(err) = self.filetransfer_send(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
SelectedEntry::None => {}
@ -71,7 +94,19 @@ impl FileTransferActivity {
let wrkdir: PathBuf = self.local().wrkdir.clone();
match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => {
self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_recv(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}
SelectedEntry::Many(entries) => {
// In case of selection: save multiple files in wrkdir/input
@ -80,8 +115,19 @@ impl FileTransferActivity {
dest_path.push(save_as);
}
// Iter files
for entry in entries.iter() {
self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None);
let entries = entries.iter().map(|x| x.get_realfile()).collect();
if let Err(err) = self.filetransfer_recv(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}
SelectedEntry::None => {}

View file

@ -49,9 +49,10 @@ use crate::fs::explorer::FileExplorer;
use crate::fs::FsEntry;
use crate::host::Localhost;
use crate::system::config_client::ConfigClient;
pub(crate) use lib::browser;
pub(self) use lib::browser;
use lib::browser::Browser;
use lib::transfer::TransferStates;
pub(self) use session::TransferPayload;
// Includes
use chrono::{DateTime, Local};

View file

@ -64,6 +64,19 @@ enum TransferErrorReason {
FileTransferError(FileTransferError),
}
/// ## TransferPayload
///
/// Represents the entity to send or receive during a transfer.
/// - File: describes an individual `FsFile` to send
/// - Any: Can be any kind of `FsEntry`, but just one
/// - Many: a list of `FsEntry`
#[derive(Debug)]
pub(super) enum TransferPayload {
File(FsFile),
Any(FsEntry),
Many(Vec<FsEntry>),
}
impl FileTransferActivity {
/// ### connect
///
@ -155,27 +168,28 @@ impl FileTransferActivity {
/// If entry is a directory, this applies to directory only
pub(super) fn filetransfer_send(
&mut self,
entry: &FsEntry,
payload: TransferPayload,
curr_remote_path: &Path,
dst_name: Option<String>,
) {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = self.get_total_transfer_size_local(entry);
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Uploading {}...", entry.get_abs_path().display()));
// Send recurse
self.filetransfer_send_recurse(entry, curr_remote_path, dst_name);
// Umount progress bar
self.umount_progress_bar();
) -> Result<(), String> {
// Use different method based on payload
match payload {
TransferPayload::Any(entry) => {
self.filetransfer_send_any(&entry, curr_remote_path, dst_name)
}
TransferPayload::File(file) => {
self.filetransfer_send_file(&file, curr_remote_path, dst_name)
}
TransferPayload::Many(entries) => {
self.filetransfer_send_many(entries, curr_remote_path)
}
}
}
/// ### filetransfer_send_one
/// ### filetransfer_send_file
///
/// Send one file to remote at specified path.
pub(super) fn filetransfer_send_one(
fn filetransfer_send_file(
&mut self,
file: &FsFile,
curr_remote_path: &Path,
@ -197,13 +211,63 @@ impl FileTransferActivity {
};
remote_path.push(remote_file_name);
// Send
let result = self.filetransfer_send_file(file, remote_path.as_path(), file_name);
let result = self.filetransfer_send_one(file, remote_path.as_path(), file_name);
// Umount progress bar
self.umount_progress_bar();
// Return result
result.map_err(|x| x.to_string())
}
/// ### filetransfer_send_any
///
/// Send a `TransferPayload` of type `Any`
fn filetransfer_send_any(
&mut self,
entry: &FsEntry,
curr_remote_path: &Path,
dst_name: Option<String>,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = self.get_total_transfer_size_local(entry);
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Uploading {}...", entry.get_abs_path().display()));
// Send recurse
self.filetransfer_send_recurse(entry, curr_remote_path, dst_name);
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
/// ### filetransfer_send_many
///
/// Send many entries to remote
fn filetransfer_send_many(
&mut self,
entries: Vec<FsEntry>,
curr_remote_path: &Path,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = entries
.iter()
.map(|x| self.get_total_transfer_size_local(x))
.sum();
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Uploading {} entries...", entries.len()));
// Send recurse
entries
.iter()
.for_each(|x| self.filetransfer_send_recurse(x, curr_remote_path, None));
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
fn filetransfer_send_recurse(
&mut self,
entry: &FsEntry,
@ -225,8 +289,7 @@ impl FileTransferActivity {
// Match entry
match entry {
FsEntry::File(file) => {
if let Err(err) =
self.filetransfer_send_file(file, remote_path.as_path(), file_name)
if let Err(err) = self.filetransfer_send_one(file, remote_path.as_path(), file_name)
{
// Log error
self.log_and_alert(
@ -329,7 +392,7 @@ impl FileTransferActivity {
/// ### filetransfer_send_file
///
/// Send local file and write it to remote path
fn filetransfer_send_file(
fn filetransfer_send_one(
&mut self,
local: &FsFile,
remote: &Path,
@ -438,11 +501,29 @@ impl FileTransferActivity {
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
pub(super) fn filetransfer_recv(
&mut self,
payload: TransferPayload,
local_path: &Path,
dst_name: Option<String>,
) -> Result<(), String> {
match payload {
TransferPayload::Any(entry) => self.filetransfer_recv_any(&entry, local_path, dst_name),
TransferPayload::File(file) => self.filetransfer_recv_file(&file, local_path),
TransferPayload::Many(entries) => self.filetransfer_recv_many(entries, local_path),
}
}
/// ### filetransfer_recv_any
///
/// Recv fs entry from remote.
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
fn filetransfer_recv_any(
&mut self,
entry: &FsEntry,
local_path: &Path,
dst_name: Option<String>,
) {
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total transfer size
@ -454,18 +535,13 @@ impl FileTransferActivity {
self.filetransfer_recv_recurse(entry, local_path, dst_name);
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
/// ### filetransfer_recv_one
/// ### filetransfer_recv_file
///
/// Receive a single file from remote.
/// Use this function instead of `filetransfer_recv_file` from external files
pub(super) fn filetransfer_recv_one(
&mut self,
entry: &FsFile,
local_path: &Path,
dst_name: String,
) -> Result<(), String> {
fn filetransfer_recv_file(&mut self, entry: &FsFile, local_path: &Path) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total transfer size
@ -474,13 +550,40 @@ impl FileTransferActivity {
// Mount progress bar
self.mount_progress_bar(format!("Downloading {}...", entry.abs_path.display()));
// Receive
let result = self.filetransfer_recv_file(local_path, entry, dst_name);
let result = self.filetransfer_recv_one(local_path, entry, entry.name.clone());
// Umount progress bar
self.umount_progress_bar();
// Return result
result.map_err(|x| x.to_string())
}
/// ### filetransfer_send_many
///
/// Send many entries to remote
fn filetransfer_recv_many(
&mut self,
entries: Vec<FsEntry>,
curr_remote_path: &Path,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = entries
.iter()
.map(|x| self.get_total_transfer_size_remote(x))
.sum();
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Downloading {} entries...", entries.len()));
// Send recurse
entries
.iter()
.for_each(|x| self.filetransfer_recv_recurse(x, curr_remote_path, None));
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
fn filetransfer_recv_recurse(
&mut self,
entry: &FsEntry,
@ -504,7 +607,7 @@ impl FileTransferActivity {
local_file_path.push(local_file_name.as_str());
// Download file
if let Err(err) =
self.filetransfer_recv_file(local_file_path.as_path(), file, file_name)
self.filetransfer_recv_one(local_file_path.as_path(), file, file_name)
{
self.log_and_alert(
LogLevel::Error,
@ -628,10 +731,10 @@ impl FileTransferActivity {
}
}
/// ### filetransfer_recv_file
/// ### filetransfer_recv_one
///
/// Receive file from remote and write it to local path
fn filetransfer_recv_file(
fn filetransfer_recv_one(
&mut self,
local: &Path,
remote: &FsFile,