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;
|
||||
}
|
||||
},
|
||||
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,
|
||||
};
|
||||
let name = path
|
||||
|
||||
@@ -23,7 +23,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
// Create file
|
||||
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(
|
||||
LogLevel::Error,
|
||||
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> {
|
||||
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())
|
||||
})?;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ use super::{Activity, Context, ExitReason};
|
||||
use crate::config::themes::Theme;
|
||||
use crate::explorer::{FileExplorer, FileSorting};
|
||||
use crate::filetransfer::{Builder, FileTransferParams};
|
||||
use crate::host::Localhost;
|
||||
use crate::host::{HostBridge, Localhost};
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::system::watcher::FsWatcher;
|
||||
|
||||
@@ -214,7 +214,7 @@ pub struct FileTransferActivity {
|
||||
/// Whether should redraw UI
|
||||
redraw: bool,
|
||||
/// Localhost bridge
|
||||
host: Localhost,
|
||||
host: Box<dyn HostBridge>,
|
||||
/// Remote host client
|
||||
client: Box<dyn RemoteFs>,
|
||||
/// Browser
|
||||
@@ -247,7 +247,7 @@ impl FileTransferActivity {
|
||||
.default_input_listener(ticks),
|
||||
),
|
||||
redraw: true,
|
||||
host,
|
||||
host: Box::new(host),
|
||||
client: Builder::build(params.protocol, params.params.clone(), &config_client),
|
||||
browser: Browser::new(&config_client),
|
||||
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
|
||||
|
||||
// Locals
|
||||
use std::fs::File as StdFile;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -26,8 +25,6 @@ const BUFSIZE: usize = 65535;
|
||||
enum TransferErrorReason {
|
||||
#[error("File transfer aborted")]
|
||||
Abrupted,
|
||||
#[error("Failed to seek file: {0}")]
|
||||
CouldNotRewind(std::io::Error),
|
||||
#[error("I/O error on localhost: {0}")]
|
||||
LocalIoError(std::io::Error),
|
||||
#[error("Host error: {0}")]
|
||||
@@ -153,7 +150,10 @@ impl FileTransferActivity {
|
||||
pub(super) fn reload_local_dir(&mut self) {
|
||||
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());
|
||||
|
||||
@@ -460,13 +460,12 @@ impl FileTransferActivity {
|
||||
}
|
||||
// Upload file
|
||||
// Try to open local file
|
||||
match self.host.open_file_read(local.path.as_path()) {
|
||||
Ok(fhnd) => match self.client.create(remote, &metadata) {
|
||||
Ok(rhnd) => {
|
||||
self.filetransfer_send_one_with_stream(local, remote, file_name, fhnd, rhnd)
|
||||
}
|
||||
match self.host.open_file(local.path.as_path()) {
|
||||
Ok(local_read) => match self.client.create(remote, &metadata) {
|
||||
Ok(rhnd) => self
|
||||
.filetransfer_send_one_with_stream(local, remote, file_name, local_read, rhnd),
|
||||
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)),
|
||||
},
|
||||
@@ -480,17 +479,18 @@ impl FileTransferActivity {
|
||||
local: &File,
|
||||
remote: &Path,
|
||||
file_name: String,
|
||||
mut reader: StdFile,
|
||||
mut reader: Box<dyn Read + Send>,
|
||||
mut writer: WriteStream,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// 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
|
||||
self.transfer.partial.init(file_size);
|
||||
// rewind
|
||||
if let Err(err) = reader.rewind() {
|
||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
||||
}
|
||||
|
||||
// Write remote file
|
||||
let mut total_bytes_written: usize = 0;
|
||||
let mut last_progress_val: f64 = 0.0;
|
||||
@@ -583,7 +583,7 @@ impl FileTransferActivity {
|
||||
local: &File,
|
||||
remote: &Path,
|
||||
file_name: String,
|
||||
mut reader: StdFile,
|
||||
reader: Box<dyn Read + Send>,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// Sync file size and attributes before transfer
|
||||
let metadata = self
|
||||
@@ -592,18 +592,19 @@ impl FileTransferActivity {
|
||||
.map_err(TransferErrorReason::HostError)
|
||||
.map(|x| x.metadata().clone())?;
|
||||
// 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
|
||||
self.transfer.partial.init(file_size);
|
||||
// rewind
|
||||
if let Err(err) = reader.rewind() {
|
||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
||||
}
|
||||
|
||||
// Draw before
|
||||
self.update_progress_bar(format!("Uploading \"{file_name}\"…"));
|
||||
self.view();
|
||||
// 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));
|
||||
}
|
||||
// set stat
|
||||
@@ -889,7 +890,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
|
||||
// Try to open local file
|
||||
match self.host.open_file_write(local) {
|
||||
match self.host.create_file(local) {
|
||||
Ok(local_file) => {
|
||||
// Download file from remote
|
||||
match self.client.open(remote.path.as_path()) {
|
||||
@@ -913,7 +914,7 @@ impl FileTransferActivity {
|
||||
remote: &File,
|
||||
file_name: String,
|
||||
mut reader: ReadStream,
|
||||
mut writer: StdFile,
|
||||
mut writer: Box<dyn Write + Send>,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
let mut total_bytes_written: usize = 0;
|
||||
// Init transfer
|
||||
@@ -1020,7 +1021,7 @@ impl FileTransferActivity {
|
||||
// Open local file
|
||||
let reader = self
|
||||
.host
|
||||
.open_file_write(local)
|
||||
.create_file(local)
|
||||
.map_err(TransferErrorReason::HostError)
|
||||
.map(Box::new)?;
|
||||
// Init transfer
|
||||
@@ -1207,7 +1208,7 @@ impl FileTransferActivity {
|
||||
// file changed
|
||||
|
||||
/// 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)
|
||||
if let Ok(local_file) = self.host.stat(local) {
|
||||
local_file.metadata().modified != remote.metadata().modified
|
||||
@@ -1231,10 +1232,10 @@ impl FileTransferActivity {
|
||||
// -- file exist
|
||||
|
||||
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 {
|
||||
self.client.stat(p).is_ok()
|
||||
self.client.exists(p).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user