mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
feat: HostBridge trait impl
This commit is contained in:
65
src/host/bridge.rs
Normal file
65
src/host/bridge.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use remotefs::fs::{Metadata, UnixPex};
|
||||||
|
use remotefs::File;
|
||||||
|
|
||||||
|
use super::HostResult;
|
||||||
|
|
||||||
|
/// Trait to bridge a remote filesystem to the host filesystem
|
||||||
|
///
|
||||||
|
/// In case of `Localhost` this should be effortless, while for remote hosts this should
|
||||||
|
/// implement a real bridge when the resource is first loaded on the local
|
||||||
|
/// filesystem and then processed on the remote.
|
||||||
|
pub trait HostBridge {
|
||||||
|
/// Print working directory
|
||||||
|
fn pwd(&mut self) -> HostResult<PathBuf>;
|
||||||
|
|
||||||
|
/// Change working directory with the new provided directory
|
||||||
|
fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult<PathBuf>;
|
||||||
|
|
||||||
|
/// Make a directory at path and update the file list (only if relative)
|
||||||
|
fn mkdir(&mut self, dir_name: &Path) -> HostResult<()> {
|
||||||
|
self.mkdir_ex(dir_name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extended option version of makedir.
|
||||||
|
/// ignex: don't report error if directory already exists
|
||||||
|
fn mkdir_ex(&mut self, dir_name: &Path, ignore_existing: bool) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Remove file entry
|
||||||
|
fn remove(&mut self, entry: &File) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Rename file or directory to new name
|
||||||
|
fn rename(&mut self, entry: &File, dst_path: &Path) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Copy file to destination path
|
||||||
|
fn copy(&mut self, entry: &File, dst: &Path) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Stat file and create a File
|
||||||
|
fn stat(&mut self, path: &Path) -> HostResult<File>;
|
||||||
|
|
||||||
|
/// Returns whether provided file path exists
|
||||||
|
fn exists(&mut self, path: &Path) -> HostResult<bool>;
|
||||||
|
|
||||||
|
/// Get content of a directory
|
||||||
|
fn list_dir(&mut self, path: &Path) -> HostResult<Vec<File>>;
|
||||||
|
|
||||||
|
/// Set file stat
|
||||||
|
fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Execute a command on localhost
|
||||||
|
fn exec(&mut self, cmd: &str) -> HostResult<String>;
|
||||||
|
|
||||||
|
/// Create a symlink from src to dst
|
||||||
|
fn symlink(&mut self, src: &Path, dst: &Path) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Change file mode to file, according to UNIX permissions
|
||||||
|
fn chmod(&mut self, path: &Path, pex: UnixPex) -> HostResult<()>;
|
||||||
|
|
||||||
|
/// Open file for reading
|
||||||
|
fn open_file(&mut self, file: &Path) -> HostResult<Box<dyn Read + Send>>;
|
||||||
|
|
||||||
|
/// Open file for writing
|
||||||
|
fn create_file(&mut self, file: &Path) -> HostResult<Box<dyn Write + Send>>;
|
||||||
|
}
|
||||||
1012
src/host/localhost.rs
Normal file
1012
src/host/localhost.rs
Normal file
File diff suppressed because it is too large
Load Diff
977
src/host/mod.rs
977
src/host/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -129,7 +129,17 @@ impl FileTransferActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FileExplorerTab::Remote => self.host.file_exists(path.as_path()),
|
FileExplorerTab::Remote => match self.host.exists(path.as_path()) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Failed to check whether {} exists on host: {}",
|
||||||
|
path.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let name = path
|
let name = path
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
// Create file
|
// Create file
|
||||||
let file_path: PathBuf = PathBuf::from(input.as_str());
|
let file_path: PathBuf = PathBuf::from(input.as_str());
|
||||||
if let Err(err) = self.host.open_file_write(file_path.as_path()) {
|
if let Err(err) = self.host.create_file(file_path.as_path()) {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
format!("Could not create file \"{}\": {}", file_path.display(), err),
|
format!("Could not create file \"{}\": {}", file_path.display(), err),
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ impl FileTransferActivity {
|
|||||||
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, WalkdirError> {
|
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, WalkdirError> {
|
||||||
let mut acc = Vec::with_capacity(32_768);
|
let mut acc = Vec::with_capacity(32_768);
|
||||||
|
|
||||||
self.walkdir(&mut acc, &self.host.pwd(), |activity, path| {
|
let pwd = self
|
||||||
|
.host
|
||||||
|
.pwd()
|
||||||
|
.map_err(|e| WalkdirError::Error(e.to_string()))?;
|
||||||
|
|
||||||
|
self.walkdir(&mut acc, &pwd, |activity, path| {
|
||||||
activity.host.list_dir(path).map_err(|e| e.to_string())
|
activity.host.list_dir(path).map_err(|e| e.to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ use super::{Activity, Context, ExitReason};
|
|||||||
use crate::config::themes::Theme;
|
use crate::config::themes::Theme;
|
||||||
use crate::explorer::{FileExplorer, FileSorting};
|
use crate::explorer::{FileExplorer, FileSorting};
|
||||||
use crate::filetransfer::{Builder, FileTransferParams};
|
use crate::filetransfer::{Builder, FileTransferParams};
|
||||||
use crate::host::Localhost;
|
use crate::host::{HostBridge, Localhost};
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use crate::system::watcher::FsWatcher;
|
use crate::system::watcher::FsWatcher;
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ pub struct FileTransferActivity {
|
|||||||
/// Whether should redraw UI
|
/// Whether should redraw UI
|
||||||
redraw: bool,
|
redraw: bool,
|
||||||
/// Localhost bridge
|
/// Localhost bridge
|
||||||
host: Localhost,
|
host: Box<dyn HostBridge>,
|
||||||
/// Remote host client
|
/// Remote host client
|
||||||
client: Box<dyn RemoteFs>,
|
client: Box<dyn RemoteFs>,
|
||||||
/// Browser
|
/// Browser
|
||||||
@@ -247,7 +247,7 @@ impl FileTransferActivity {
|
|||||||
.default_input_listener(ticks),
|
.default_input_listener(ticks),
|
||||||
),
|
),
|
||||||
redraw: true,
|
redraw: true,
|
||||||
host,
|
host: Box::new(host),
|
||||||
client: Builder::build(params.protocol, params.params.clone(), &config_client),
|
client: Builder::build(params.protocol, params.params.clone(), &config_client),
|
||||||
browser: Browser::new(&config_client),
|
browser: Browser::new(&config_client),
|
||||||
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
use std::fs::File as StdFile;
|
use std::io::{Read, Write};
|
||||||
use std::io::{Read, Seek, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
@@ -26,8 +25,6 @@ const BUFSIZE: usize = 65535;
|
|||||||
enum TransferErrorReason {
|
enum TransferErrorReason {
|
||||||
#[error("File transfer aborted")]
|
#[error("File transfer aborted")]
|
||||||
Abrupted,
|
Abrupted,
|
||||||
#[error("Failed to seek file: {0}")]
|
|
||||||
CouldNotRewind(std::io::Error),
|
|
||||||
#[error("I/O error on localhost: {0}")]
|
#[error("I/O error on localhost: {0}")]
|
||||||
LocalIoError(std::io::Error),
|
LocalIoError(std::io::Error),
|
||||||
#[error("Host error: {0}")]
|
#[error("Host error: {0}")]
|
||||||
@@ -153,7 +150,10 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn reload_local_dir(&mut self) {
|
pub(super) fn reload_local_dir(&mut self) {
|
||||||
self.mount_blocking_wait("Loading local directory...");
|
self.mount_blocking_wait("Loading local directory...");
|
||||||
|
|
||||||
let wrkdir: PathBuf = self.host.pwd();
|
let Ok(wrkdir) = self.host.pwd() else {
|
||||||
|
error!("failed to get host working directory");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let res = self.local_scan(wrkdir.as_path());
|
let res = self.local_scan(wrkdir.as_path());
|
||||||
|
|
||||||
@@ -460,13 +460,12 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
// Upload file
|
// Upload file
|
||||||
// Try to open local file
|
// Try to open local file
|
||||||
match self.host.open_file_read(local.path.as_path()) {
|
match self.host.open_file(local.path.as_path()) {
|
||||||
Ok(fhnd) => match self.client.create(remote, &metadata) {
|
Ok(local_read) => match self.client.create(remote, &metadata) {
|
||||||
Ok(rhnd) => {
|
Ok(rhnd) => self
|
||||||
self.filetransfer_send_one_with_stream(local, remote, file_name, fhnd, rhnd)
|
.filetransfer_send_one_with_stream(local, remote, file_name, local_read, rhnd),
|
||||||
}
|
|
||||||
Err(err) if err.kind == RemoteErrorType::UnsupportedFeature => {
|
Err(err) if err.kind == RemoteErrorType::UnsupportedFeature => {
|
||||||
self.filetransfer_send_one_wno_stream(local, remote, file_name, fhnd)
|
self.filetransfer_send_one_wno_stream(local, remote, file_name, local_read)
|
||||||
}
|
}
|
||||||
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
||||||
},
|
},
|
||||||
@@ -480,17 +479,18 @@ impl FileTransferActivity {
|
|||||||
local: &File,
|
local: &File,
|
||||||
remote: &Path,
|
remote: &Path,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
mut reader: StdFile,
|
mut reader: Box<dyn Read + Send>,
|
||||||
mut writer: WriteStream,
|
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 = self
|
||||||
|
.host
|
||||||
|
.stat(local.path())
|
||||||
|
.map_err(TransferErrorReason::HostError)
|
||||||
|
.map(|x| x.metadata().size as usize)?;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
self.transfer.partial.init(file_size);
|
self.transfer.partial.init(file_size);
|
||||||
// rewind
|
|
||||||
if let Err(err) = reader.rewind() {
|
|
||||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
|
||||||
}
|
|
||||||
// Write remote file
|
// Write remote file
|
||||||
let mut total_bytes_written: usize = 0;
|
let mut total_bytes_written: usize = 0;
|
||||||
let mut last_progress_val: f64 = 0.0;
|
let mut last_progress_val: f64 = 0.0;
|
||||||
@@ -583,7 +583,7 @@ impl FileTransferActivity {
|
|||||||
local: &File,
|
local: &File,
|
||||||
remote: &Path,
|
remote: &Path,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
mut reader: StdFile,
|
reader: Box<dyn Read + Send>,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
// Sync file size and attributes before transfer
|
// Sync file size and attributes before transfer
|
||||||
let metadata = self
|
let metadata = self
|
||||||
@@ -592,18 +592,19 @@ impl FileTransferActivity {
|
|||||||
.map_err(TransferErrorReason::HostError)
|
.map_err(TransferErrorReason::HostError)
|
||||||
.map(|x| x.metadata().clone())?;
|
.map(|x| x.metadata().clone())?;
|
||||||
// Write file
|
// Write file
|
||||||
let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
|
let file_size = self
|
||||||
|
.host
|
||||||
|
.stat(local.path())
|
||||||
|
.map_err(TransferErrorReason::HostError)
|
||||||
|
.map(|x| x.metadata().size as usize)?;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
self.transfer.partial.init(file_size);
|
self.transfer.partial.init(file_size);
|
||||||
// rewind
|
|
||||||
if let Err(err) = reader.rewind() {
|
|
||||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
|
||||||
}
|
|
||||||
// Draw before
|
// Draw before
|
||||||
self.update_progress_bar(format!("Uploading \"{file_name}\"…"));
|
self.update_progress_bar(format!("Uploading \"{file_name}\"…"));
|
||||||
self.view();
|
self.view();
|
||||||
// Send file
|
// Send file
|
||||||
if let Err(err) = self.client.create_file(remote, &metadata, Box::new(reader)) {
|
if let Err(err) = self.client.create_file(remote, &metadata, reader) {
|
||||||
return Err(TransferErrorReason::FileTransferError(err));
|
return Err(TransferErrorReason::FileTransferError(err));
|
||||||
}
|
}
|
||||||
// set stat
|
// set stat
|
||||||
@@ -889,7 +890,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to open local file
|
// Try to open local file
|
||||||
match self.host.open_file_write(local) {
|
match self.host.create_file(local) {
|
||||||
Ok(local_file) => {
|
Ok(local_file) => {
|
||||||
// Download file from remote
|
// Download file from remote
|
||||||
match self.client.open(remote.path.as_path()) {
|
match self.client.open(remote.path.as_path()) {
|
||||||
@@ -913,7 +914,7 @@ impl FileTransferActivity {
|
|||||||
remote: &File,
|
remote: &File,
|
||||||
file_name: String,
|
file_name: String,
|
||||||
mut reader: ReadStream,
|
mut reader: ReadStream,
|
||||||
mut writer: StdFile,
|
mut writer: Box<dyn Write + Send>,
|
||||||
) -> Result<(), TransferErrorReason> {
|
) -> Result<(), TransferErrorReason> {
|
||||||
let mut total_bytes_written: usize = 0;
|
let mut total_bytes_written: usize = 0;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
@@ -1020,7 +1021,7 @@ impl FileTransferActivity {
|
|||||||
// Open local file
|
// Open local file
|
||||||
let reader = self
|
let reader = self
|
||||||
.host
|
.host
|
||||||
.open_file_write(local)
|
.create_file(local)
|
||||||
.map_err(TransferErrorReason::HostError)
|
.map_err(TransferErrorReason::HostError)
|
||||||
.map(Box::new)?;
|
.map(Box::new)?;
|
||||||
// Init transfer
|
// Init transfer
|
||||||
@@ -1207,7 +1208,7 @@ impl FileTransferActivity {
|
|||||||
// file changed
|
// file changed
|
||||||
|
|
||||||
/// Check whether provided file has changed on local disk, compared to remote file
|
/// Check whether provided file has changed on local disk, compared to remote file
|
||||||
fn has_local_file_changed(&self, local: &Path, remote: &File) -> bool {
|
fn has_local_file_changed(&mut self, local: &Path, remote: &File) -> bool {
|
||||||
// check if files are equal (in case, don't transfer)
|
// check if files are equal (in case, don't transfer)
|
||||||
if let Ok(local_file) = self.host.stat(local) {
|
if let Ok(local_file) = self.host.stat(local) {
|
||||||
local_file.metadata().modified != remote.metadata().modified
|
local_file.metadata().modified != remote.metadata().modified
|
||||||
@@ -1231,10 +1232,10 @@ impl FileTransferActivity {
|
|||||||
// -- file exist
|
// -- file exist
|
||||||
|
|
||||||
pub(crate) fn local_file_exists(&mut self, p: &Path) -> bool {
|
pub(crate) fn local_file_exists(&mut self, p: &Path) -> bool {
|
||||||
self.host.file_exists(p)
|
self.host.exists(p).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remote_file_exists(&mut self, p: &Path) -> bool {
|
pub(crate) fn remote_file_exists(&mut self, p: &Path) -> bool {
|
||||||
self.client.stat(p).is_ok()
|
self.client.exists(p).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user