From 87f9369041de06d19fdb0747653c2b95c0cf3e24 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 5 Oct 2024 18:22:56 +0200 Subject: [PATCH] feat: HostBridge trait impl --- src/host/bridge.rs | 65 ++ src/host/localhost.rs | 1012 +++++++++++++++++ src/host/mod.rs | 977 +--------------- .../filetransfer/actions/change_dir.rs | 12 +- .../filetransfer/actions/newfile.rs | 2 +- .../filetransfer/actions/walkdir.rs | 7 +- src/ui/activities/filetransfer/mod.rs | 6 +- src/ui/activities/filetransfer/session.rs | 61 +- 8 files changed, 1141 insertions(+), 1001 deletions(-) create mode 100644 src/host/bridge.rs create mode 100644 src/host/localhost.rs diff --git a/src/host/bridge.rs b/src/host/bridge.rs new file mode 100644 index 0000000..1dfd598 --- /dev/null +++ b/src/host/bridge.rs @@ -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; + + /// Change working directory with the new provided directory + fn change_wrkdir(&mut self, new_dir: &Path) -> HostResult; + + /// 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; + + /// Returns whether provided file path exists + fn exists(&mut self, path: &Path) -> HostResult; + + /// Get content of a directory + fn list_dir(&mut self, path: &Path) -> HostResult>; + + /// Set file stat + fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()>; + + /// Execute a command on localhost + fn exec(&mut self, cmd: &str) -> HostResult; + + /// 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>; + + /// Open file for writing + fn create_file(&mut self, file: &Path) -> HostResult>; +} diff --git a/src/host/localhost.rs b/src/host/localhost.rs new file mode 100644 index 0000000..0ea9f23 --- /dev/null +++ b/src/host/localhost.rs @@ -0,0 +1,1012 @@ +use std::fs::{self, OpenOptions}; +use std::io::{Read, Write}; +use std::os::unix::fs::PermissionsExt as _; +use std::path::{Path, PathBuf}; + +use filetime::FileTime; +use remotefs::fs::{FileType, Metadata, UnixPex}; +use remotefs::File; + +use super::{HostBridge, HostResult}; +use crate::host::{HostError, HostErrorType}; +use crate::utils::path; + +/// Localhost is the entity which holds the information about the current directory and host. +/// It provides functions to navigate across the local host file system +pub struct Localhost { + wrkdir: PathBuf, + files: Vec, +} + +impl Localhost { + /// Instantiates a new Localhost struct + pub fn new(wrkdir: PathBuf) -> HostResult { + debug!("Initializing localhost at {}", wrkdir.display()); + let mut host: Localhost = Localhost { + wrkdir, + files: Vec::new(), + }; + // Check if dir exists + let pwd = host.pwd()?; + + if !host.exists(&pwd)? { + error!( + "Failed to initialize localhost: {} doesn't exist", + host.wrkdir.display() + ); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + host.wrkdir.as_path(), + )); + } + // Retrieve files for provided path + host.files = match host.list_dir(&pwd) { + Ok(files) => files, + Err(err) => { + error!( + "Failed to initialize localhost: could not scan wrkdir: {}", + err + ); + return Err(err); + } + }; + info!("Localhost initialized with success"); + Ok(host) + } + + /// Convert path to absolute path + fn to_path(&self, p: &Path) -> PathBuf { + path::absolutize(self.wrkdir.as_path(), p) + } +} + +impl HostBridge for Localhost { + fn pwd(&mut self) -> HostResult { + Ok(self.wrkdir.clone()) + } + + fn change_wrkdir(&mut self, new_dir: &std::path::Path) -> HostResult { + let new_dir: PathBuf = self.to_path(new_dir); + info!("Changing localhost directory to {}...", new_dir.display()); + // Check whether directory exists + if !self.exists(new_dir.as_path())? { + error!("Could not change directory: No such file or directory"); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + new_dir.as_path(), + )); + } + // Change directory + if let Err(err) = std::env::set_current_dir(new_dir.as_path()) { + error!("Could not enter directory: {}", err); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + Some(err), + new_dir.as_path(), + )); + } + let prev_dir: PathBuf = self.wrkdir.clone(); // Backup location + // Update working directory + // Change dir + self.wrkdir = new_dir; + // Scan new directory + let pwd = self.pwd()?; + self.files = match self.list_dir(&pwd) { + Ok(files) => files, + Err(err) => { + error!("Could not scan new directory: {}", err); + // Restore directory + self.wrkdir = prev_dir; + return Err(err); + } + }; + debug!("Changed directory to {}", self.wrkdir.display()); + Ok(self.wrkdir.clone()) + } + + fn mkdir_ex(&mut self, dir_name: &std::path::Path, ignore_existing: bool) -> HostResult<()> { + let dir_path: PathBuf = self.to_path(dir_name); + info!("Making directory {}", dir_path.display()); + // If dir already exists, return Error + if dir_path.exists() { + match ignore_existing { + true => return Ok(()), + false => { + return Err(HostError::new( + HostErrorType::FileAlreadyExists, + None, + dir_path.as_path(), + )) + } + } + } + match std::fs::create_dir(dir_path.as_path()) { + Ok(_) => { + // Update dir + if dir_name.is_relative() { + let pwd = self.pwd()?; + self.files = self.list_dir(&pwd)?; + } + info!("Created directory {}", dir_path.display()); + Ok(()) + } + Err(err) => { + error!("Could not make directory: {}", err); + Err(HostError::new( + HostErrorType::CouldNotCreateFile, + Some(err), + dir_path.as_path(), + )) + } + } + } + + fn remove(&mut self, entry: &File) -> HostResult<()> { + if entry.is_dir() { + // If file doesn't exist; return error + debug!("Removing directory {}", entry.path().display()); + if !entry.path().exists() { + error!("Directory doesn't exist"); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + entry.path(), + )); + } + // Remove + match std::fs::remove_dir_all(entry.path()) { + Ok(_) => { + // Update dir + let pwd = self.pwd()?; + self.files = self.list_dir(&pwd)?; + info!("Removed directory {}", entry.path().display()); + Ok(()) + } + Err(err) => { + error!("Could not remove directory: {}", err); + Err(HostError::new( + HostErrorType::DeleteFailed, + Some(err), + entry.path(), + )) + } + } + } else { + // If file doesn't exist; return error + debug!("Removing file {}", entry.path().display()); + if !entry.path().exists() { + error!("File doesn't exist"); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + entry.path(), + )); + } + // Remove + match std::fs::remove_file(entry.path()) { + Ok(_) => { + // Update dir + let pwd = self.pwd()?; + self.files = self.list_dir(&pwd)?; + info!("Removed file {}", entry.path().display()); + Ok(()) + } + Err(err) => { + error!("Could not remove file: {}", err); + Err(HostError::new( + HostErrorType::DeleteFailed, + Some(err), + entry.path(), + )) + } + } + } + } + + fn rename(&mut self, entry: &File, dst_path: &std::path::Path) -> HostResult<()> { + match std::fs::rename(entry.path(), dst_path) { + Ok(_) => { + // Scan dir + let pwd = self.pwd()?; + self.files = self.list_dir(&pwd)?; + debug!( + "Moved file {} to {}", + entry.path().display(), + dst_path.display() + ); + Ok(()) + } + Err(err) => { + error!( + "Failed to move {} to {}: {}", + entry.path().display(), + dst_path.display(), + err + ); + Err(HostError::new( + HostErrorType::CouldNotCreateFile, + Some(err), + entry.path(), + )) + } + } + } + + fn copy(&mut self, entry: &File, dst: &std::path::Path) -> HostResult<()> { + // Get absolute path of dest + let dst: PathBuf = self.to_path(dst); + info!( + "Copying file {} to {}", + entry.path().display(), + dst.display() + ); + // Match entry + if entry.is_dir() { + // If destination path doesn't exist, create destination + if !dst.exists() { + debug!("Directory {} doesn't exist; creating it", dst.display()); + self.mkdir(dst.as_path())?; + } + // Scan dir + let dir_files: Vec = self.list_dir(entry.path())?; + // Iterate files + 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 + let pwd = self.pwd()?; + match dst.is_dir() { + true => { + if dst == pwd { + self.files = self.list_dir(&pwd)?; + } else if let Some(parent) = dst.parent() { + // If parent is pwd, scan directory + if parent == pwd { + self.files = self.list_dir(&pwd)?; + } + } + } + false => { + if let Some(parent) = dst.parent() { + // If parent is pwd, scan directory + if parent == pwd { + self.files = self.list_dir(&pwd)?; + } + } + } + } + Ok(()) + } + + fn stat(&mut self, path: &std::path::Path) -> HostResult { + info!("Stating file {}", path.display()); + let path: PathBuf = self.to_path(path); + 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 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 + Ok(File { path, metadata }) + } + + fn exists(&mut self, path: &Path) -> HostResult { + Ok(path.exists()) + } + + fn list_dir(&mut self, path: &Path) -> HostResult> { + info!("Reading directory {}", path.display()); + match std::fs::read_dir(path) { + Ok(e) => { + let mut fs_entries: Vec = Vec::new(); + for entry in e.flatten() { + // NOTE: 0.4.1, don't fail if stat for one file fails + match self.stat(entry.path().as_path()) { + Ok(entry) => fs_entries.push(entry), + Err(e) => error!("Failed to stat {}: {}", entry.path().display(), e), + } + } + Ok(fs_entries) + } + Err(err) => Err(HostError::new( + HostErrorType::DirNotAccessible, + Some(err), + path, + )), + } + } + + fn setstat(&mut self, path: &Path, metadata: &Metadata) -> HostResult<()> { + debug!("Setting stat for file at {}", path.display()); + if let Some(mtime) = metadata.modified { + let mtime = FileTime::from_system_time(mtime); + debug!("setting mtime {:?}", mtime); + filetime::set_file_mtime(path, mtime) + .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?; + } + if let Some(atime) = metadata.accessed { + let atime = FileTime::from_system_time(atime); + filetime::set_file_atime(path, atime) + .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?; + } + #[cfg(unix)] + if let Some(mode) = metadata.mode { + self.chmod(path, mode)?; + } + Ok(()) + } + + fn exec(&mut self, cmd: &str) -> HostResult { + // Make command + let args: Vec<&str> = cmd.split(' ').collect(); + let cmd: &str = args.first().unwrap(); + let argv: &[&str] = &args[1..]; + info!("Executing command: {} {:?}", cmd, argv); + match std::process::Command::new(cmd).args(argv).output() { + Ok(output) => match std::str::from_utf8(&output.stdout) { + Ok(s) => { + info!("Command output: {}", s); + Ok(s.to_string()) + } + Err(_) => Ok(String::new()), + }, + Err(err) => { + error!("Failed to run command: {}", err); + Err(HostError::new( + HostErrorType::ExecutionFailed, + Some(err), + self.wrkdir.as_path(), + )) + } + } + } + + fn symlink(&mut self, src: &Path, dst: &Path) -> HostResult<()> { + if cfg!(windows) { + warn!("Cannot create symlink on Windows"); + + return Err(HostError::from(HostErrorType::NotImplemented)); + } + + let src = self.to_path(src); + std::os::unix::fs::symlink(dst, src.as_path()).map_err(|e| { + error!( + "Failed to create symlink at {} pointing at {}: {}", + src.display(), + dst.display(), + e + ); + HostError::new(HostErrorType::CouldNotCreateFile, Some(e), src.as_path()) + }) + } + + fn chmod(&mut self, path: &std::path::Path, pex: UnixPex) -> HostResult<()> { + if cfg!(windows) { + warn!("Cannot set file mode on Windows"); + + return Err(HostError::from(HostErrorType::NotImplemented)); + } + + let path: PathBuf = self.to_path(path); + // Get metadta + match fs::metadata(path.as_path()) { + Ok(metadata) => { + let mut mpex = metadata.permissions(); + mpex.set_mode(pex.into()); + match fs::set_permissions(path.as_path(), mpex) { + Ok(_) => { + info!("Changed mode for {} to {:?}", path.display(), pex); + Ok(()) + } + Err(err) => { + error!("Could not change mode for file {}: {}", path.display(), err); + Err(HostError::new( + HostErrorType::FileNotAccessible, + Some(err), + path.as_path(), + )) + } + } + } + Err(err) => { + error!( + "Chmod failed; could not read metadata for file {}: {}", + path.display(), + err + ); + Err(HostError::new( + HostErrorType::FileNotAccessible, + Some(err), + path.as_path(), + )) + } + } + } + + fn open_file(&mut self, file: &std::path::Path) -> HostResult> { + let file: PathBuf = self.to_path(file); + info!("Opening file {} for read", file.display()); + if !self.exists(file.as_path())? { + error!("File doesn't exist!"); + return Err(HostError::new( + HostErrorType::NoSuchFileOrDirectory, + None, + file.as_path(), + )); + } + match OpenOptions::new() + .create(false) + .read(true) + .write(false) + .open(file.as_path()) + { + Ok(f) => Ok(Box::new(f)), + Err(err) => { + error!("Could not open file for read: {}", err); + Err(HostError::new( + HostErrorType::FileNotAccessible, + Some(err), + file.as_path(), + )) + } + } + } + + fn create_file(&mut self, file: &Path) -> HostResult> { + let file: PathBuf = self.to_path(file); + info!("Opening file {} for write", file.display()); + match OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(file.as_path()) + { + Ok(f) => Ok(Box::new(f)), + Err(err) => { + error!("Failed to open file: {}", err); + match self.exists(file.as_path())? { + true => Err(HostError::new( + HostErrorType::ReadonlyFile, + Some(err), + file.as_path(), + )), + false => Err(HostError::new( + HostErrorType::FileNotAccessible, + Some(err), + file.as_path(), + )), + } + } + } + } +} + +#[cfg(test)] +mod tests { + + #[cfg(unix)] + use std::fs::File as StdFile; + #[cfg(unix)] + use std::io::Write; + use std::ops::AddAssign; + #[cfg(unix)] + use std::os::unix::fs::{symlink, PermissionsExt}; + use std::time::{Duration, SystemTime}; + + use pretty_assertions::assert_eq; + + use super::*; + #[cfg(unix)] + use crate::utils::test_helpers::make_fsentry; + use crate::utils::test_helpers::{create_sample_file, make_file_at}; + + #[test] + fn test_host_error_new() { + let error: HostError = + HostError::new(HostErrorType::CouldNotCreateFile, None, Path::new("/tmp")); + assert!(error.ioerr.is_none()); + assert_eq!(error.path.as_ref().unwrap(), Path::new("/tmp")); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_new() { + let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + assert_eq!(host.wrkdir, PathBuf::from("/dev")); + // Scan dir + let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter += 1; + } + assert_eq!(host.files.len(), counter); + } + + #[test] + #[cfg(windows)] + fn test_host_localhost_new() { + let mut host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap(); + assert_eq!(host.wrkdir, PathBuf::from("C:\\users")); + // Scan dir + let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter = counter + 1; + } + assert_eq!(host.files.len(), counter); + } + + #[test] + #[should_panic] + fn test_host_localhost_new_bad() { + Localhost::new(PathBuf::from("/omargabber/123/345")) + .ok() + .unwrap(); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_pwd() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + assert_eq!(host.pwd().unwrap(), PathBuf::from("/dev")); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_change_dir() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + let new_dir: PathBuf = PathBuf::from("/dev"); + assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); + // Verify new files + // Scan dir + let entries = std::fs::read_dir(new_dir.as_path()).unwrap(); + let mut counter: usize = 0; + for _ in entries { + counter += 1; + } + assert_eq!(host.files.len(), counter); + } + + #[test] + #[cfg(unix)] + #[should_panic] + fn test_host_localhost_change_dir_failed() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + let new_dir: PathBuf = PathBuf::from("/omar/gabber/123/456"); + assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_open_read() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + // Create temp file + let file: tempfile::NamedTempFile = create_sample_file(); + assert!(host.open_file(file.path()).is_ok()); + } + + #[test] + #[cfg(unix)] + #[should_panic] + fn test_host_localhost_open_read_err_no_such_file() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + assert!(host + .open_file(PathBuf::from("/bin/foo-bar-test-omar-123-456-789.txt").as_path()) + .is_ok()); + } + + #[test] + #[cfg(any(target_os = "macos", target_os = "linux"))] + fn test_host_localhost_open_read_err_not_accessible() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + let file: tempfile::NamedTempFile = create_sample_file(); + //let mut perms = fs::metadata(file.path())?.permissions(); + fs::set_permissions(file.path(), PermissionsExt::from_mode(0o222)).unwrap(); + //fs::set_permissions(file.path(), perms)?; + assert!(host.open_file(file.path()).is_err()); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_open_write() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + // Create temp file + let file: tempfile::NamedTempFile = create_sample_file(); + assert!(host.create_file(file.path()).is_ok()); + } + + #[test] + #[cfg(any(target_os = "macos", target_os = "linux"))] + fn test_host_localhost_open_write_err() { + let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); + let file: tempfile::NamedTempFile = create_sample_file(); + //let mut perms = fs::metadata(file.path())?.permissions(); + fs::set_permissions(file.path(), PermissionsExt::from_mode(0o444)).unwrap(); + //fs::set_permissions(file.path(), perms)?; + assert!(host.create_file(file.path()).is_err()); + } + + #[cfg(unix)] + #[test] + fn test_host_localhost_symlinks() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create sample file + assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok()); + // Create symlink + assert!(symlink( + format!("{}/foo.txt", tmpdir.path().display()), + format!("{}/bar.txt", tmpdir.path().display()) + ) + .is_ok()); + // Get dir + let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let files: Vec = host.files.clone(); + // Verify files + let file_0: &File = files.get(0).unwrap(); + if file_0.name() == *"foo.txt" { + assert!(file_0.metadata.symlink.is_none()); + } else { + assert_eq!( + file_0.metadata.symlink.as_ref().unwrap(), + &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) + ); + } + // Verify simlink + let file_1: &File = files.get(1).unwrap(); + if file_1.name() == *"bar.txt" { + assert_eq!( + file_1.metadata.symlink.as_ref().unwrap(), + &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) + ); + } else { + assert!(file_1.metadata.symlink.is_none()); + } + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_mkdir() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let files: Vec = host.files.clone(); + assert_eq!(files.len(), 0); // There should be 0 files now + assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); + let files: Vec = host.files.clone(); + assert_eq!(files.len(), 1); // There should be 1 file now + // Try to re-create directory + assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err()); + // Try abs path + assert!(host + .mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true) + .is_ok()); + // Fail + assert!(host + .mkdir_ex( + PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(), + true + ) + .is_err()); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_remove() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create sample file + 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 files: Vec = host.files.clone(); + assert_eq!(files.len(), 1); // There should be 1 file now + // Remove file + assert!(host.remove(files.get(0).unwrap()).is_ok()); + // There should be 0 files now + let files: Vec = host.files.clone(); + assert_eq!(files.len(), 0); // There should be 0 files now + // Create directory + assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); + // Delete directory + let files: Vec = host.files.clone(); + assert_eq!(files.len(), 1); // There should be 1 file now + assert!(host.remove(files.get(0).unwrap()).is_ok()); + // Remove unexisting directory + assert!(host + .remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) + .is_err()); + assert!(host + .remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false)) + .is_err()); + } + + #[test] + #[cfg(unix)] + fn test_host_localhost_rename() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create sample file + let src_path: PathBuf = + PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str()); + assert!(StdFile::create(src_path.as_path()).is_ok()); + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let files: Vec = host.files.clone(); + assert_eq!(files.len(), 1); // There should be 1 file now + assert_eq!(files.get(0).unwrap().name(), "foo.txt"); + // Rename file + let dst_path: PathBuf = + PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); + assert!(host + .rename(files.get(0).unwrap(), dst_path.as_path()) + .is_ok()); + // There should be still 1 file now, but named bar.txt + let files: Vec = host.files.clone(); + assert_eq!(files.len(), 1); // There should be 0 files now + assert_eq!(files.get(0).unwrap().name(), "bar.txt"); + // Fail + let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); + assert!(host + .rename(files.get(0).unwrap(), bad_path.as_path()) + .is_err()); + } + + #[test] + fn should_setstat() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + let file: tempfile::NamedTempFile = create_sample_file(); + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + // stat + let mut filemeta = host.stat(file.path()).unwrap(); + + let mut new_atime = SystemTime::UNIX_EPOCH; + new_atime.add_assign(Duration::from_secs(1612164210)); + + let mut new_mtime = SystemTime::UNIX_EPOCH; + new_mtime.add_assign(Duration::from_secs(1613160210)); + + filemeta.metadata.accessed = Some(new_atime); + filemeta.metadata.modified = Some(new_mtime); + + // setstat + assert!(host.setstat(filemeta.path(), filemeta.metadata()).is_ok()); + let new_metadata = host.stat(file.path()).unwrap(); + + assert_eq!(new_metadata.metadata().accessed, Some(new_atime)); + assert_eq!(new_metadata.metadata().modified, Some(new_mtime)); + } + + #[cfg(unix)] + #[test] + fn test_host_chmod() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + let file: tempfile::NamedTempFile = create_sample_file(); + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + // Chmod to file + assert!(host.chmod(file.path(), UnixPex::from(0o755)).is_ok()); + // Chmod to dir + assert!(host.chmod(tmpdir.path(), UnixPex::from(0o750)).is_ok()); + // Error + assert!(host + .chmod( + Path::new("/tmp/krgiogoiegj/kwrgnoerig"), + UnixPex::from(0o777) + ) + .is_err()); + } + + #[cfg(unix)] + #[test] + fn test_host_copy_file_absolute() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create file in tmpdir + let mut file1_path: PathBuf = PathBuf::from(tmpdir.path()); + file1_path.push("foo.txt"); + // Write file 1 + let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); + assert!(file1.write_all(b"Hello world!\n").is_ok()); + // Get file 2 path + let mut file2_path: PathBuf = PathBuf::from(tmpdir.path()); + file2_path.push("bar.txt"); + // Create host + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let file1_entry: File = host.files.get(0).unwrap().clone(); + assert_eq!(file1_entry.name(), String::from("foo.txt")); + // Copy + assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); + // Verify host has two files + assert_eq!(host.files.len(), 2); + // Fail copy + assert!(host + .copy( + &make_fsentry(PathBuf::from("/a/a7/a/a7a"), false), + PathBuf::from("571k422i").as_path() + ) + .is_err()); + } + + #[cfg(unix)] + #[test] + fn test_host_copy_file_relative() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create file in tmpdir + let mut file1_path: PathBuf = PathBuf::from(tmpdir.path()); + file1_path.push("foo.txt"); + // Write file 1 + let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); + assert!(file1.write_all(b"Hello world!\n").is_ok()); + // Get file 2 path + let file2_path: PathBuf = PathBuf::from("bar.txt"); + // Create host + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let file1_entry: File = host.files.get(0).unwrap().clone(); + assert_eq!(file1_entry.name(), String::from("foo.txt")); + // Copy + assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); + // Verify host has two files + assert_eq!(host.files.len(), 2); + } + + #[cfg(unix)] + #[test] + fn test_host_copy_directory_absolute() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create directory in tmpdir + let mut dir_src: PathBuf = PathBuf::from(tmpdir.path()); + dir_src.push("test_dir/"); + assert!(std::fs::create_dir(dir_src.as_path()).is_ok()); + // Create file in src dir + let mut file1_path: PathBuf = dir_src.clone(); + file1_path.push("foo.txt"); + // Write file 1 + let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); + assert!(file1.write_all(b"Hello world!\n").is_ok()); + // Copy dir src to dir ddest + let mut dir_dest: PathBuf = PathBuf::from(tmpdir.path()); + dir_dest.push("test_dest_dir/"); + // Create host + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let dir_src_entry: File = host.files.get(0).unwrap().clone(); + assert_eq!(dir_src_entry.name(), String::from("test_dir")); + // Copy + assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); + // Verify host has two files + assert_eq!(host.files.len(), 2); + // Verify dir_dest contains foo.txt + let mut test_file_path: PathBuf = dir_dest.clone(); + test_file_path.push("foo.txt"); + assert!(host.stat(test_file_path.as_path()).is_ok()); + } + + #[cfg(unix)] + #[test] + fn test_host_copy_directory_relative() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + // Create directory in tmpdir + let mut dir_src: PathBuf = PathBuf::from(tmpdir.path()); + dir_src.push("test_dir/"); + assert!(std::fs::create_dir(dir_src.as_path()).is_ok()); + // Create file in src dir + let mut file1_path: PathBuf = dir_src.clone(); + file1_path.push("foo.txt"); + // Write file 1 + let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); + assert!(file1.write_all(b"Hello world!\n").is_ok()); + // Copy dir src to dir ddest + let dir_dest: PathBuf = PathBuf::from("test_dest_dir/"); + // Create host + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + let dir_src_entry: File = host.files.get(0).unwrap().clone(); + assert_eq!(dir_src_entry.name(), String::from("test_dir")); + // Copy + assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); + // Verify host has two files + assert_eq!(host.files.len(), 2); + // Verify dir_dest contains foo.txt + let mut test_file_path: PathBuf = dir_dest.clone(); + test_file_path.push("foo.txt"); + assert!(host.stat(test_file_path.as_path()).is_ok()); + } + + #[test] + fn test_host_exec() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); + // Execute + assert!(host.exec("echo 5").ok().unwrap().as_str().contains("5")); + } + + #[cfg(unix)] + #[test] + fn should_create_symlink() { + let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); + let dir_path: &Path = tmpdir.path(); + // Make file + assert!(make_file_at(dir_path, "pippo.txt").is_ok()); + let mut host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); + let mut p = dir_path.to_path_buf(); + p.push("pippo.txt"); + // Make symlink + assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_ok()); + // Fail symlink + assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_err()); + assert!(host + .symlink(Path::new("/tmp/oooo/aaaa"), p.as_path()) + .is_err()); + } + + #[test] + fn test_host_fmt_error() { + let err: HostError = HostError::new( + HostErrorType::CouldNotCreateFile, + Some(std::io::Error::from(std::io::ErrorKind::AddrInUse)), + Path::new("/tmp"), + ); + assert_eq!( + format!("{err}"), + String::from("Could not create file: address in use (/tmp)"), + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::DeleteFailed)), + String::from("Could not delete file") + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::ExecutionFailed)), + String::from("Command execution failed"), + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::DirNotAccessible)), + String::from("Could not access directory"), + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::NoSuchFileOrDirectory)), + String::from("No such file or directory") + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::ReadonlyFile)), + String::from("File is readonly") + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::FileNotAccessible)), + String::from("Could not access file") + ); + assert_eq!( + format!("{}", HostError::from(HostErrorType::FileAlreadyExists)), + String::from("File already exists") + ); + } +} diff --git a/src/host/mod.rs b/src/host/mod.rs index 2dc0a39..5270888 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -2,23 +2,18 @@ //! //! `host` is the module which provides functionalities to host file system -// ext -// Metadata ext -#[cfg(unix)] -use std::fs::set_permissions; -use std::fs::{self, File as StdFile, OpenOptions}; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; +mod bridge; +mod localhost; + use std::path::{Path, PathBuf}; -use filetime::{self, FileTime}; -#[cfg(unix)] -use remotefs::fs::UnixPex; -use remotefs::fs::{File, FileType, Metadata}; use thiserror::Error; // Locals -use crate::utils::path; +pub use self::bridge::HostBridge; +pub use self::localhost::Localhost; + +pub type HostResult = Result; /// HostErrorType provides an overview of the specific host error #[derive(Error, Debug)] @@ -39,6 +34,10 @@ pub enum HostErrorType { ExecutionFailed, #[error("Could not delete file")] DeleteFailed, + #[error("Not implemented")] + NotImplemented, + #[error("remote fs error: {0}")] + RemoteFs(#[from] remotefs::RemoteError), } /// HostError is a wrapper for the error type and the exact io error @@ -83,964 +82,12 @@ impl std::fmt::Display for HostError { } } -/// Localhost is the entity which holds the information about the current directory and host. -/// It provides functions to navigate across the local host file system -pub struct Localhost { - wrkdir: PathBuf, - files: Vec, -} - -impl Localhost { - /// Instantiates a new Localhost struct - pub fn new(wrkdir: PathBuf) -> Result { - debug!("Initializing localhost at {}", wrkdir.display()); - let mut host: Localhost = Localhost { - wrkdir, - files: Vec::new(), - }; - // Check if dir exists - if !host.file_exists(host.wrkdir.as_path()) { - error!( - "Failed to initialize localhost: {} doesn't exist", - host.wrkdir.display() - ); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - host.wrkdir.as_path(), - )); - } - // Retrieve files for provided path - host.files = match host.list_dir(host.wrkdir.as_path()) { - Ok(files) => files, - Err(err) => { - error!( - "Failed to initialize localhost: could not scan wrkdir: {}", - err - ); - return Err(err); - } - }; - info!("Localhost initialized with success"); - Ok(host) - } - - /// Print working directory - pub fn pwd(&self) -> PathBuf { - self.wrkdir.clone() - } - - /// Change working directory with the new provided directory - pub fn change_wrkdir(&mut self, new_dir: &Path) -> Result { - let new_dir: PathBuf = self.to_path(new_dir); - info!("Changing localhost directory to {}...", new_dir.display()); - // Check whether directory exists - if !self.file_exists(new_dir.as_path()) { - error!("Could not change directory: No such file or directory"); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - new_dir.as_path(), - )); - } - // Change directory - if let Err(err) = std::env::set_current_dir(new_dir.as_path()) { - error!("Could not enter directory: {}", err); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - Some(err), - new_dir.as_path(), - )); - } - let prev_dir: PathBuf = self.wrkdir.clone(); // Backup location - // Update working directory - // Change dir - self.wrkdir = new_dir; - // Scan new directory - self.files = match self.list_dir(self.wrkdir.as_path()) { - Ok(files) => files, - Err(err) => { - error!("Could not scan new directory: {}", err); - // Restore directory - self.wrkdir = prev_dir; - return Err(err); - } - }; - debug!("Changed directory to {}", self.wrkdir.display()); - Ok(self.wrkdir.clone()) - } - - /// Make a directory at path and update the file list (only if relative) - pub fn mkdir(&mut self, dir_name: &Path) -> Result<(), HostError> { - self.mkdir_ex(dir_name, false) - } - - /// Extended option version of makedir. - /// ignex: don't report error if directory already exists - pub fn mkdir_ex(&mut self, dir_name: &Path, ignex: bool) -> Result<(), HostError> { - let dir_path: PathBuf = self.to_path(dir_name); - info!("Making directory {}", dir_path.display()); - // If dir already exists, return Error - if dir_path.exists() { - match ignex { - true => return Ok(()), - false => { - return Err(HostError::new( - HostErrorType::FileAlreadyExists, - None, - dir_path.as_path(), - )) - } - } - } - match std::fs::create_dir(dir_path.as_path()) { - Ok(_) => { - // Update dir - if dir_name.is_relative() { - self.files = self.list_dir(self.wrkdir.as_path())?; - } - info!("Created directory {}", dir_path.display()); - Ok(()) - } - Err(err) => { - error!("Could not make directory: {}", err); - Err(HostError::new( - HostErrorType::CouldNotCreateFile, - Some(err), - dir_path.as_path(), - )) - } - } - } - - /// Remove file entry - pub fn remove(&mut self, entry: &File) -> Result<(), HostError> { - if entry.is_dir() { - // If file doesn't exist; return error - debug!("Removing directory {}", entry.path().display()); - if !entry.path().exists() { - error!("Directory doesn't exist"); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - entry.path(), - )); - } - // Remove - match std::fs::remove_dir_all(entry.path()) { - Ok(_) => { - // Update dir - self.files = self.list_dir(self.wrkdir.as_path())?; - info!("Removed directory {}", entry.path().display()); - Ok(()) - } - Err(err) => { - error!("Could not remove directory: {}", err); - Err(HostError::new( - HostErrorType::DeleteFailed, - Some(err), - entry.path(), - )) - } - } - } else { - // If file doesn't exist; return error - debug!("Removing file {}", entry.path().display()); - if !entry.path().exists() { - error!("File doesn't exist"); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - entry.path(), - )); - } - // Remove - match std::fs::remove_file(entry.path()) { - Ok(_) => { - // Update dir - self.files = self.list_dir(self.wrkdir.as_path())?; - info!("Removed file {}", entry.path().display()); - Ok(()) - } - Err(err) => { - error!("Could not remove file: {}", err); - Err(HostError::new( - HostErrorType::DeleteFailed, - Some(err), - entry.path(), - )) - } - } - } - } - - /// Rename file or directory to new name - pub fn rename(&mut self, entry: &File, dst_path: &Path) -> Result<(), HostError> { - match std::fs::rename(entry.path(), dst_path) { - Ok(_) => { - // Scan dir - self.files = self.list_dir(self.wrkdir.as_path())?; - debug!( - "Moved file {} to {}", - entry.path().display(), - dst_path.display() - ); - Ok(()) - } - Err(err) => { - error!( - "Failed to move {} to {}: {}", - entry.path().display(), - dst_path.display(), - err - ); - Err(HostError::new( - HostErrorType::CouldNotCreateFile, - Some(err), - entry.path(), - )) - } - } - } - - /// Copy file to destination path - pub fn copy(&mut self, entry: &File, dst: &Path) -> Result<(), HostError> { - // Get absolute path of dest - let dst: PathBuf = self.to_path(dst); - info!( - "Copying file {} to {}", - entry.path().display(), - dst.display() - ); - // Match entry - if entry.is_dir() { - // If destination path doesn't exist, create destination - if !dst.exists() { - debug!("Directory {} doesn't exist; creating it", dst.display()); - self.mkdir(dst.as_path())?; - } - // Scan dir - let dir_files: Vec = self.list_dir(entry.path())?; - // Iterate files - 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 - match dst.is_dir() { - true => { - if dst == self.pwd().as_path() { - self.files = self.list_dir(self.wrkdir.as_path())?; - } else if let Some(parent) = dst.parent() { - // If parent is pwd, scan directory - if parent == self.pwd().as_path() { - self.files = self.list_dir(self.wrkdir.as_path())?; - } - } - } - false => { - if let Some(parent) = dst.parent() { - // If parent is pwd, scan directory - if parent == self.pwd().as_path() { - self.files = self.list_dir(self.wrkdir.as_path())?; - } - } - } - } - Ok(()) - } - - /// Stat file and create a File - pub fn stat(&self, path: &Path) -> Result { - info!("Stating file {}", path.display()); - let path: PathBuf = self.to_path(path); - 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 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 - Ok(File { path, metadata }) - } - - /// Set file stat - pub fn setstat(&self, path: &Path, metadata: &Metadata) -> Result<(), HostError> { - debug!("Setting stat for file at {}", path.display()); - if let Some(mtime) = metadata.modified { - let mtime = FileTime::from_system_time(mtime); - debug!("setting mtime {:?}", mtime); - filetime::set_file_mtime(path, mtime) - .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?; - } - if let Some(atime) = metadata.accessed { - let atime = FileTime::from_system_time(atime); - filetime::set_file_atime(path, atime) - .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?; - } - #[cfg(unix)] - if let Some(mode) = metadata.mode { - self.chmod(path, mode)?; - } - Ok(()) - } - - /// Execute a command on localhost - pub fn exec(&self, cmd: &str) -> Result { - // Make command - let args: Vec<&str> = cmd.split(' ').collect(); - let cmd: &str = args.first().unwrap(); - let argv: &[&str] = &args[1..]; - info!("Executing command: {} {:?}", cmd, argv); - match std::process::Command::new(cmd).args(argv).output() { - Ok(output) => match std::str::from_utf8(&output.stdout) { - Ok(s) => { - info!("Command output: {}", s); - Ok(s.to_string()) - } - Err(_) => Ok(String::new()), - }, - Err(err) => { - error!("Failed to run command: {}", err); - Err(HostError::new( - HostErrorType::ExecutionFailed, - Some(err), - self.wrkdir.as_path(), - )) - } - } - } - - /// Change file mode to file, according to UNIX permissions - #[cfg(unix)] - pub fn chmod(&self, path: &Path, pex: UnixPex) -> Result<(), HostError> { - let path: PathBuf = self.to_path(path); - // Get metadta - match fs::metadata(path.as_path()) { - Ok(metadata) => { - let mut mpex = metadata.permissions(); - mpex.set_mode(pex.into()); - match set_permissions(path.as_path(), mpex) { - Ok(_) => { - info!("Changed mode for {} to {:?}", path.display(), pex); - Ok(()) - } - Err(err) => { - error!("Could not change mode for file {}: {}", path.display(), err); - Err(HostError::new( - HostErrorType::FileNotAccessible, - Some(err), - path.as_path(), - )) - } - } - } - Err(err) => { - error!( - "Chmod failed; could not read metadata for file {}: {}", - path.display(), - err - ); - Err(HostError::new( - HostErrorType::FileNotAccessible, - Some(err), - path.as_path(), - )) - } - } - } - - /// Open file for read - pub fn open_file_read(&self, file: &Path) -> Result { - let file: PathBuf = self.to_path(file); - info!("Opening file {} for read", file.display()); - if !self.file_exists(file.as_path()) { - error!("File doesn't exist!"); - return Err(HostError::new( - HostErrorType::NoSuchFileOrDirectory, - None, - file.as_path(), - )); - } - match OpenOptions::new() - .create(false) - .read(true) - .write(false) - .open(file.as_path()) - { - Ok(f) => Ok(f), - Err(err) => { - error!("Could not open file for read: {}", err); - Err(HostError::new( - HostErrorType::FileNotAccessible, - Some(err), - file.as_path(), - )) - } - } - } - - /// Open file for write - pub fn open_file_write(&self, file: &Path) -> Result { - let file: PathBuf = self.to_path(file); - info!("Opening file {} for write", file.display()); - match OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(file.as_path()) - { - Ok(f) => Ok(f), - Err(err) => { - error!("Failed to open file: {}", err); - match self.file_exists(file.as_path()) { - true => Err(HostError::new( - HostErrorType::ReadonlyFile, - Some(err), - file.as_path(), - )), - false => Err(HostError::new( - HostErrorType::FileNotAccessible, - Some(err), - file.as_path(), - )), - } - } - } - } - - /// Returns whether provided file path exists - pub fn file_exists(&self, path: &Path) -> bool { - path.exists() - } - - /// Get content of the current directory as a list of fs entry - pub fn list_dir(&self, dir: &Path) -> Result, HostError> { - info!("Reading directory {}", dir.display()); - match std::fs::read_dir(dir) { - Ok(e) => { - let mut fs_entries: Vec = Vec::new(); - for entry in e.flatten() { - // NOTE: 0.4.1, don't fail if stat for one file fails - match self.stat(entry.path().as_path()) { - Ok(entry) => fs_entries.push(entry), - Err(e) => error!("Failed to stat {}: {}", entry.path().display(), e), - } - } - Ok(fs_entries) - } - Err(err) => Err(HostError::new( - HostErrorType::DirNotAccessible, - Some(err), - dir, - )), - } - } - - /// Create a symlink at path pointing at target - #[cfg(unix)] - pub fn symlink(&self, path: &Path, target: &Path) -> Result<(), HostError> { - let path = self.to_path(path); - std::os::unix::fs::symlink(target, path.as_path()).map_err(|e| { - error!( - "Failed to create symlink at {} pointing at {}: {}", - path.display(), - target.display(), - e - ); - HostError::new(HostErrorType::CouldNotCreateFile, Some(e), path.as_path()) - }) - } - - /// Convert path to absolute path - fn to_path(&self, p: &Path) -> PathBuf { - path::absolutize(self.wrkdir.as_path(), p) - } -} - #[cfg(test)] -mod tests { - - #[cfg(unix)] - use std::fs::File as StdFile; - #[cfg(unix)] - use std::io::Write; - use std::ops::AddAssign; - #[cfg(unix)] - use std::os::unix::fs::{symlink, PermissionsExt}; - use std::time::{Duration, SystemTime}; +mod test { use pretty_assertions::assert_eq; use super::*; - #[cfg(unix)] - use crate::utils::test_helpers::make_fsentry; - use crate::utils::test_helpers::{create_sample_file, make_file_at}; - - #[test] - fn test_host_error_new() { - let error: HostError = - HostError::new(HostErrorType::CouldNotCreateFile, None, Path::new("/tmp")); - assert!(error.ioerr.is_none()); - assert_eq!(error.path.as_ref().unwrap(), Path::new("/tmp")); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_new() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - assert_eq!(host.wrkdir, PathBuf::from("/dev")); - // Scan dir - let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap(); - let mut counter: usize = 0; - for _ in entries { - counter += 1; - } - assert_eq!(host.files.len(), counter); - } - - #[test] - #[cfg(windows)] - fn test_host_localhost_new() { - let host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap(); - assert_eq!(host.wrkdir, PathBuf::from("C:\\users")); - // Scan dir - let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap(); - let mut counter: usize = 0; - for _ in entries { - counter = counter + 1; - } - assert_eq!(host.files.len(), counter); - } - - #[test] - #[should_panic] - fn test_host_localhost_new_bad() { - Localhost::new(PathBuf::from("/omargabber/123/345")) - .ok() - .unwrap(); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_pwd() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - assert_eq!(host.pwd(), PathBuf::from("/dev")); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_change_dir() { - let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - let new_dir: PathBuf = PathBuf::from("/dev"); - assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); - // Verify new files - // Scan dir - let entries = std::fs::read_dir(new_dir.as_path()).unwrap(); - let mut counter: usize = 0; - for _ in entries { - counter += 1; - } - assert_eq!(host.files.len(), counter); - } - - #[test] - #[cfg(unix)] - #[should_panic] - fn test_host_localhost_change_dir_failed() { - let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - let new_dir: PathBuf = PathBuf::from("/omar/gabber/123/456"); - assert!(host.change_wrkdir(new_dir.as_path()).is_ok()); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_open_read() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - // Create temp file - let file: tempfile::NamedTempFile = create_sample_file(); - assert!(host.open_file_read(file.path()).is_ok()); - } - - #[test] - #[cfg(unix)] - #[should_panic] - fn test_host_localhost_open_read_err_no_such_file() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - assert!(host - .open_file_read(PathBuf::from("/bin/foo-bar-test-omar-123-456-789.txt").as_path()) - .is_ok()); - } - - #[test] - #[cfg(any(target_os = "macos", target_os = "linux"))] - fn test_host_localhost_open_read_err_not_accessible() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - let file: tempfile::NamedTempFile = create_sample_file(); - //let mut perms = fs::metadata(file.path())?.permissions(); - fs::set_permissions(file.path(), PermissionsExt::from_mode(0o222)).unwrap(); - //fs::set_permissions(file.path(), perms)?; - assert!(host.open_file_read(file.path()).is_err()); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_open_write() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - // Create temp file - let file: tempfile::NamedTempFile = create_sample_file(); - assert!(host.open_file_write(file.path()).is_ok()); - } - - #[test] - #[cfg(any(target_os = "macos", target_os = "linux"))] - fn test_host_localhost_open_write_err() { - let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap(); - let file: tempfile::NamedTempFile = create_sample_file(); - //let mut perms = fs::metadata(file.path())?.permissions(); - fs::set_permissions(file.path(), PermissionsExt::from_mode(0o444)).unwrap(); - //fs::set_permissions(file.path(), perms)?; - assert!(host.open_file_write(file.path()).is_err()); - } - - #[cfg(unix)] - #[test] - fn test_host_localhost_symlinks() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create sample file - assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok()); - // Create symlink - assert!(symlink( - format!("{}/foo.txt", tmpdir.path().display()), - format!("{}/bar.txt", tmpdir.path().display()) - ) - .is_ok()); - // Get dir - let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.files.clone(); - // Verify files - let file_0: &File = files.get(0).unwrap(); - if file_0.name() == *"foo.txt" { - assert!(file_0.metadata.symlink.is_none()); - } else { - assert_eq!( - file_0.metadata.symlink.as_ref().unwrap(), - &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) - ); - } - // Verify simlink - let file_1: &File = files.get(1).unwrap(); - if file_1.name() == *"bar.txt" { - assert_eq!( - file_1.metadata.symlink.as_ref().unwrap(), - &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display())) - ); - } else { - assert!(file_1.metadata.symlink.is_none()); - } - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_mkdir() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.files.clone(); - assert_eq!(files.len(), 0); // There should be 0 files now - assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); - let files: Vec = host.files.clone(); - assert_eq!(files.len(), 1); // There should be 1 file now - // Try to re-create directory - assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err()); - // Try abs path - assert!(host - .mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true) - .is_ok()); - // Fail - assert!(host - .mkdir_ex( - PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(), - true - ) - .is_err()); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_remove() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create sample file - 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 files: Vec = host.files.clone(); - assert_eq!(files.len(), 1); // There should be 1 file now - // Remove file - assert!(host.remove(files.get(0).unwrap()).is_ok()); - // There should be 0 files now - let files: Vec = host.files.clone(); - assert_eq!(files.len(), 0); // There should be 0 files now - // Create directory - assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok()); - // Delete directory - let files: Vec = host.files.clone(); - assert_eq!(files.len(), 1); // There should be 1 file now - assert!(host.remove(files.get(0).unwrap()).is_ok()); - // Remove unexisting directory - assert!(host - .remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) - .is_err()); - assert!(host - .remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false)) - .is_err()); - } - - #[test] - #[cfg(unix)] - fn test_host_localhost_rename() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create sample file - let src_path: PathBuf = - PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str()); - assert!(StdFile::create(src_path.as_path()).is_ok()); - let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let files: Vec = host.files.clone(); - assert_eq!(files.len(), 1); // There should be 1 file now - assert_eq!(files.get(0).unwrap().name(), "foo.txt"); - // Rename file - let dst_path: PathBuf = - PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); - assert!(host - .rename(files.get(0).unwrap(), dst_path.as_path()) - .is_ok()); - // There should be still 1 file now, but named bar.txt - let files: Vec = host.files.clone(); - assert_eq!(files.len(), 1); // There should be 0 files now - assert_eq!(files.get(0).unwrap().name(), "bar.txt"); - // Fail - let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); - assert!(host - .rename(files.get(0).unwrap(), bad_path.as_path()) - .is_err()); - } - - #[test] - fn should_setstat() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - let file: tempfile::NamedTempFile = create_sample_file(); - let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - // stat - let mut filemeta = host.stat(file.path()).unwrap(); - - let mut new_atime = SystemTime::UNIX_EPOCH; - new_atime.add_assign(Duration::from_secs(1612164210)); - - let mut new_mtime = SystemTime::UNIX_EPOCH; - new_mtime.add_assign(Duration::from_secs(1613160210)); - - filemeta.metadata.accessed = Some(new_atime); - filemeta.metadata.modified = Some(new_mtime); - - // setstat - assert!(host.setstat(filemeta.path(), filemeta.metadata()).is_ok()); - let new_metadata = host.stat(file.path()).unwrap(); - - assert_eq!(new_metadata.metadata().accessed, Some(new_atime)); - assert_eq!(new_metadata.metadata().modified, Some(new_mtime)); - } - - #[cfg(unix)] - #[test] - fn test_host_chmod() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - let file: tempfile::NamedTempFile = create_sample_file(); - let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - // Chmod to file - assert!(host.chmod(file.path(), UnixPex::from(0o755)).is_ok()); - // Chmod to dir - assert!(host.chmod(tmpdir.path(), UnixPex::from(0o750)).is_ok()); - // Error - assert!(host - .chmod( - Path::new("/tmp/krgiogoiegj/kwrgnoerig"), - UnixPex::from(0o777) - ) - .is_err()); - } - - #[cfg(unix)] - #[test] - fn test_host_copy_file_absolute() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create file in tmpdir - let mut file1_path: PathBuf = PathBuf::from(tmpdir.path()); - file1_path.push("foo.txt"); - // Write file 1 - let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); - assert!(file1.write_all(b"Hello world!\n").is_ok()); - // Get file 2 path - let mut file2_path: PathBuf = PathBuf::from(tmpdir.path()); - file2_path.push("bar.txt"); - // Create host - let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: File = host.files.get(0).unwrap().clone(); - assert_eq!(file1_entry.name(), String::from("foo.txt")); - // Copy - assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); - // Verify host has two files - assert_eq!(host.files.len(), 2); - // Fail copy - assert!(host - .copy( - &make_fsentry(PathBuf::from("/a/a7/a/a7a"), false), - PathBuf::from("571k422i").as_path() - ) - .is_err()); - } - - #[cfg(unix)] - #[test] - fn test_host_copy_file_relative() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create file in tmpdir - let mut file1_path: PathBuf = PathBuf::from(tmpdir.path()); - file1_path.push("foo.txt"); - // Write file 1 - let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); - assert!(file1.write_all(b"Hello world!\n").is_ok()); - // Get file 2 path - let file2_path: PathBuf = PathBuf::from("bar.txt"); - // Create host - let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: File = host.files.get(0).unwrap().clone(); - assert_eq!(file1_entry.name(), String::from("foo.txt")); - // Copy - assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); - // Verify host has two files - assert_eq!(host.files.len(), 2); - } - - #[cfg(unix)] - #[test] - fn test_host_copy_directory_absolute() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create directory in tmpdir - let mut dir_src: PathBuf = PathBuf::from(tmpdir.path()); - dir_src.push("test_dir/"); - assert!(std::fs::create_dir(dir_src.as_path()).is_ok()); - // Create file in src dir - let mut file1_path: PathBuf = dir_src.clone(); - file1_path.push("foo.txt"); - // Write file 1 - let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); - assert!(file1.write_all(b"Hello world!\n").is_ok()); - // Copy dir src to dir ddest - let mut dir_dest: PathBuf = PathBuf::from(tmpdir.path()); - dir_dest.push("test_dest_dir/"); - // Create host - let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let dir_src_entry: File = host.files.get(0).unwrap().clone(); - assert_eq!(dir_src_entry.name(), String::from("test_dir")); - // Copy - assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); - // Verify host has two files - assert_eq!(host.files.len(), 2); - // Verify dir_dest contains foo.txt - let mut test_file_path: PathBuf = dir_dest.clone(); - test_file_path.push("foo.txt"); - assert!(host.stat(test_file_path.as_path()).is_ok()); - } - - #[cfg(unix)] - #[test] - fn test_host_copy_directory_relative() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - // Create directory in tmpdir - let mut dir_src: PathBuf = PathBuf::from(tmpdir.path()); - dir_src.push("test_dir/"); - assert!(std::fs::create_dir(dir_src.as_path()).is_ok()); - // Create file in src dir - let mut file1_path: PathBuf = dir_src.clone(); - file1_path.push("foo.txt"); - // Write file 1 - let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap(); - assert!(file1.write_all(b"Hello world!\n").is_ok()); - // Copy dir src to dir ddest - let dir_dest: PathBuf = PathBuf::from("test_dest_dir/"); - // Create host - let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let dir_src_entry: File = host.files.get(0).unwrap().clone(); - assert_eq!(dir_src_entry.name(), String::from("test_dir")); - // Copy - assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); - // Verify host has two files - assert_eq!(host.files.len(), 2); - // Verify dir_dest contains foo.txt - let mut test_file_path: PathBuf = dir_dest.clone(); - test_file_path.push("foo.txt"); - assert!(host.stat(test_file_path.as_path()).is_ok()); - } - - #[test] - fn test_host_exec() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - // Execute - assert!(host.exec("echo 5").ok().unwrap().as_str().contains("5")); - } - - #[cfg(unix)] - #[test] - fn should_create_symlink() { - let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); - let dir_path: &Path = tmpdir.path(); - // Make file - assert!(make_file_at(dir_path, "pippo.txt").is_ok()); - let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap(); - let mut p = dir_path.to_path_buf(); - p.push("pippo.txt"); - // Make symlink - assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_ok()); - // Fail symlink - assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_err()); - assert!(host - .symlink(Path::new("/tmp/oooo/aaaa"), p.as_path()) - .is_err()); - } #[test] fn test_host_fmt_error() { diff --git a/src/ui/activities/filetransfer/actions/change_dir.rs b/src/ui/activities/filetransfer/actions/change_dir.rs index 975b0cf..e4c5be1 100644 --- a/src/ui/activities/filetransfer/actions/change_dir.rs +++ b/src/ui/activities/filetransfer/actions/change_dir.rs @@ -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 diff --git a/src/ui/activities/filetransfer/actions/newfile.rs b/src/ui/activities/filetransfer/actions/newfile.rs index bc43f14..98136c8 100644 --- a/src/ui/activities/filetransfer/actions/newfile.rs +++ b/src/ui/activities/filetransfer/actions/newfile.rs @@ -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), diff --git a/src/ui/activities/filetransfer/actions/walkdir.rs b/src/ui/activities/filetransfer/actions/walkdir.rs index b18ef1d..3c8bf4c 100644 --- a/src/ui/activities/filetransfer/actions/walkdir.rs +++ b/src/ui/activities/filetransfer/actions/walkdir.rs @@ -18,7 +18,12 @@ impl FileTransferActivity { pub(crate) fn action_walkdir_local(&mut self) -> Result, 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()) })?; diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 9ea44ea..1aa402a 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -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, /// Remote host client client: Box, /// 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 diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index c536a0d..b472310 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -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, 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, ) -> 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, ) -> 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() } }