Copy method (host/transfer)

This commit is contained in:
ChristianVisintin
2020-12-21 10:49:31 +01:00
parent 3220d00b14
commit 08728bf55e
5 changed files with 292 additions and 18 deletions

View File

@@ -344,6 +344,16 @@ impl FileTransfer for FtpFileTransfer {
} }
} }
/// ### copy
///
/// Copy file to destination
fn copy(&mut self, _src: &FsEntry, _dst: &Path) -> Result<(), FileTransferError> {
// FTP doesn't support file copy
Err(FileTransferError::new(
FileTransferErrorType::UnsupportedFeature,
))
}
/// ### list_dir /// ### list_dir
/// ///
/// List directory entries /// List directory entries
@@ -796,6 +806,35 @@ mod tests {
assert!(ftp.disconnect().is_ok()); assert!(ftp.disconnect().is_ok());
} }
#[test]
fn test_filetransfer_sftp_copy() {
let mut ftp: FtpFileTransfer = FtpFileTransfer::new(false);
// Connect
assert!(ftp
.connect(String::from("speedtest.tele2.net"), 21, None, None)
.is_ok());
// Pwd
assert_eq!(ftp.pwd().ok().unwrap(), PathBuf::from("/"));
// Copy
let file: FsFile = FsFile {
name: String::from("readme.txt"),
abs_path: PathBuf::from("/readme.txt"),
last_change_time: SystemTime::UNIX_EPOCH,
last_access_time: SystemTime::UNIX_EPOCH,
creation_time: SystemTime::UNIX_EPOCH,
size: 0,
ftype: Some(String::from("txt")), // File type
readonly: true,
symlink: None, // UNIX only
user: Some(0), // UNIX only
group: Some(0), // UNIX only
unix_pex: Some((6, 4, 4)), // UNIX only
};
assert!(ftp
.copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt"))
.is_err());
}
/* NOTE: they don't work /* NOTE: they don't work
#[test] #[test]
fn test_filetransfer_ftp_list_dir() { fn test_filetransfer_ftp_list_dir() {

View File

@@ -157,6 +157,11 @@ pub trait FileTransfer {
fn change_dir(&mut self, dir: &Path) -> Result<PathBuf, FileTransferError>; fn change_dir(&mut self, dir: &Path) -> Result<PathBuf, FileTransferError>;
/// ### copy
///
/// Copy file to destination
fn copy(&mut self, src: &FsEntry, dst: &Path) -> Result<(), FileTransferError>;
/// ### list_dir /// ### list_dir
/// ///
/// List directory entries /// List directory entries

View File

@@ -468,6 +468,47 @@ impl FileTransfer for ScpFileTransfer {
} }
} }
/// ### copy
///
/// Copy file to destination
fn copy(&mut self, src: &FsEntry, dst: &Path) -> Result<(), FileTransferError> {
match self.is_connected() {
true => {
// Run `cp -rf`
let p: PathBuf = self.wrkdir.clone();
match self.perform_shell_cmd_with_path(
p.as_path(),
format!(
"cp -rf \"{}\" \"{}\"; echo $?",
src.get_abs_path().display(),
dst.display()
)
.as_str(),
) {
Ok(output) =>
// Check if output is 0
{
match output.as_str().trim() == "0" {
true => Ok(()), // File copied
false => Err(FileTransferError::new_ex(
// Could not copy file
FileTransferErrorType::FileCreateDenied,
format!("\"{}\"", dst.display()),
)),
}
}
Err(err) => Err(FileTransferError::new_ex(
FileTransferErrorType::ProtocolError,
format!("{}", err),
)),
}
}
false => Err(FileTransferError::new(
FileTransferErrorType::UninitializedSession,
)),
}
}
/// ### list_dir /// ### list_dir
/// ///
/// List directory entries /// List directory entries

View File

@@ -348,6 +348,16 @@ impl FileTransfer for SftpFileTransfer {
} }
} }
/// ### copy
///
/// Copy file to destination
fn copy(&mut self, _src: &FsEntry, _dst: &Path) -> Result<(), FileTransferError> {
// SFTP doesn't support file copy
Err(FileTransferError::new(
FileTransferErrorType::UnsupportedFeature,
))
}
/// ### list_dir /// ### list_dir
/// ///
/// List directory entries /// List directory entries
@@ -701,6 +711,41 @@ mod tests {
assert!(client.disconnect().is_ok()); assert!(client.disconnect().is_ok());
} }
#[test]
fn test_filetransfer_sftp_copy() {
let mut client: SftpFileTransfer = SftpFileTransfer::new();
assert!(client
.connect(
String::from("test.rebex.net"),
22,
Some(String::from("demo")),
Some(String::from("password"))
)
.is_ok());
// Check session and sftp
assert!(client.session.is_some());
assert!(client.sftp.is_some());
assert_eq!(client.wrkdir, PathBuf::from("/"));
// Copy
let file: FsFile = FsFile {
name: String::from("readme.txt"),
abs_path: PathBuf::from("/readme.txt"),
last_change_time: SystemTime::UNIX_EPOCH,
last_access_time: SystemTime::UNIX_EPOCH,
creation_time: SystemTime::UNIX_EPOCH,
size: 0,
ftype: Some(String::from("txt")), // File type
readonly: true,
symlink: None, // UNIX only
user: Some(0), // UNIX only
group: Some(0), // UNIX only
unix_pex: Some((6, 4, 4)), // UNIX only
};
assert!(client
.copy(&FsEntry::File(file), &Path::new("/tmp/dest.txt"))
.is_err());
}
#[test] #[test]
fn test_filetransfer_sftp_cwd_error() { fn test_filetransfer_sftp_cwd_error() {
let mut client: SftpFileTransfer = SftpFileTransfer::new(); let mut client: SftpFileTransfer = SftpFileTransfer::new();

View File

@@ -28,9 +28,9 @@ use std::path::{Path, PathBuf};
use std::time::SystemTime; use std::time::SystemTime;
// Metadata ext // Metadata ext
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
use std::fs::set_permissions; use std::fs::set_permissions;
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
use std::os::unix::fs::{MetadataExt, PermissionsExt};
// Locals // Locals
use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::fs::{FsDirectory, FsEntry, FsFile};
@@ -187,10 +187,7 @@ impl Localhost {
Ok(_) => { Ok(_) => {
// Update dir // Update dir
if dir_name.is_relative() { if dir_name.is_relative() {
self.files = match self.scan_dir(self.wrkdir.as_path()) { self.files = self.scan_dir(self.wrkdir.as_path())?;
Ok(f) => f,
Err(err) => return Err(err),
};
} }
Ok(()) Ok(())
} }
@@ -212,10 +209,7 @@ impl Localhost {
match std::fs::remove_dir_all(dir.abs_path.as_path()) { match std::fs::remove_dir_all(dir.abs_path.as_path()) {
Ok(_) => { Ok(_) => {
// Update dir // Update dir
self.files = match self.scan_dir(self.wrkdir.as_path()) { self.files = self.scan_dir(self.wrkdir.as_path())?;
Ok(f) => f,
Err(err) => return Err(err),
};
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new(HostErrorType::DeleteFailed, Some(err))), Err(err) => Err(HostError::new(HostErrorType::DeleteFailed, Some(err))),
@@ -230,10 +224,7 @@ impl Localhost {
match std::fs::remove_file(file.abs_path.as_path()) { match std::fs::remove_file(file.abs_path.as_path()) {
Ok(_) => { Ok(_) => {
// Update dir // Update dir
self.files = match self.scan_dir(self.wrkdir.as_path()) { self.files = self.scan_dir(self.wrkdir.as_path())?;
Ok(f) => f,
Err(err) => return Err(err),
};
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new(HostErrorType::DeleteFailed, Some(err))), Err(err) => Err(HostError::new(HostErrorType::DeleteFailed, Some(err))),
@@ -250,16 +241,85 @@ impl Localhost {
match std::fs::rename(abs_path.as_path(), dst_path) { match std::fs::rename(abs_path.as_path(), dst_path) {
Ok(_) => { Ok(_) => {
// Scan dir // Scan dir
self.files = match self.scan_dir(self.wrkdir.as_path()) { self.files = self.scan_dir(self.wrkdir.as_path())?;
Ok(f) => f,
Err(err) => return Err(err),
};
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new(HostErrorType::CouldNotCreateFile, Some(err))), Err(err) => Err(HostError::new(HostErrorType::CouldNotCreateFile, Some(err))),
} }
} }
/// ### copy
///
/// Copy file to destination path
pub fn copy(&mut self, entry: &FsEntry, dst: &Path) -> Result<(), HostError> {
// Get absolute path of dest
let dst: PathBuf = match dst.is_absolute() {
true => PathBuf::from(dst),
false => {
let mut p: PathBuf = self.wrkdir.clone();
p.push(dst);
p
}
};
// Match entry
match entry {
FsEntry::File(file) => {
// 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(file.name.as_str());
p
}
false => dst.clone(),
};
// Copy entry path to dst path
if let Err(err) = std::fs::copy(file.abs_path.as_path(), dst.as_path()) {
return Err(HostError::new(HostErrorType::CouldNotCreateFile, Some(err)));
}
}
FsEntry::Directory(dir) => {
// If destination path doesn't exist, create destination
if !dst.exists() {
self.mkdir(dst.as_path())?;
}
// Scan dir
let dir_files: Vec<FsEntry> = self.scan_dir(dir.abs_path.as_path())?;
// Iterate files
for dir_entry in dir_files.iter() {
// Calculate dst
let mut sub_dst: PathBuf = dst.clone();
sub_dst.push(dir_entry.get_name());
// Call function recursively
self.copy(dir_entry, sub_dst.as_path())?;
}
}
}
// Reload directory if dst is pwd
match dst.is_dir() {
true => {
if dst == self.pwd().as_path() {
self.files = self.scan_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.scan_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.scan_dir(self.wrkdir.as_path())?;
}
}
}
}
Ok(())
}
/// ### stat /// ### stat
/// ///
/// Stat file and create a FsEntry /// Stat file and create a FsEntry
@@ -768,6 +828,90 @@ mod tests {
.is_err()); .is_err());
} }
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[test]
fn test_host_copy_file() {
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: File = File::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: FsEntry = host.files.get(0).unwrap().clone();
assert_eq!(file1_entry.get_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(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[test]
fn test_hop_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: File = File::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: FsEntry = host.files.get(0).unwrap().clone();
assert_eq!(dir_src_entry.get_name(), String::from(""));
// 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(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[test]
fn test_hop_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: File = File::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: FsEntry = host.files.get(0).unwrap().clone();
assert_eq!(dir_src_entry.get_name(), String::from(""));
// 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] #[test]
fn test_host_fmt_error() { fn test_host_fmt_error() {
let err: HostError = HostError::new( let err: HostError = HostError::new(