mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Copy method (host/transfer)
This commit is contained in:
@@ -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 directory entries
|
||||
@@ -796,6 +806,35 @@ mod tests {
|
||||
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
|
||||
#[test]
|
||||
fn test_filetransfer_ftp_list_dir() {
|
||||
|
||||
@@ -157,6 +157,11 @@ pub trait FileTransfer {
|
||||
|
||||
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 directory entries
|
||||
|
||||
@@ -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 directory entries
|
||||
|
||||
@@ -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 directory entries
|
||||
@@ -701,6 +711,41 @@ mod tests {
|
||||
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]
|
||||
fn test_filetransfer_sftp_cwd_error() {
|
||||
let mut client: SftpFileTransfer = SftpFileTransfer::new();
|
||||
|
||||
180
src/host/mod.rs
180
src/host/mod.rs
@@ -28,9 +28,9 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
// Metadata ext
|
||||
#[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;
|
||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
|
||||
// Locals
|
||||
use crate::fs::{FsDirectory, FsEntry, FsFile};
|
||||
@@ -187,10 +187,7 @@ impl Localhost {
|
||||
Ok(_) => {
|
||||
// Update dir
|
||||
if dir_name.is_relative() {
|
||||
self.files = match self.scan_dir(self.wrkdir.as_path()) {
|
||||
Ok(f) => f,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -212,10 +209,7 @@ impl Localhost {
|
||||
match std::fs::remove_dir_all(dir.abs_path.as_path()) {
|
||||
Ok(_) => {
|
||||
// Update dir
|
||||
self.files = match self.scan_dir(self.wrkdir.as_path()) {
|
||||
Ok(f) => f,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
Ok(())
|
||||
}
|
||||
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()) {
|
||||
Ok(_) => {
|
||||
// Update dir
|
||||
self.files = match self.scan_dir(self.wrkdir.as_path()) {
|
||||
Ok(f) => f,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
Ok(())
|
||||
}
|
||||
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) {
|
||||
Ok(_) => {
|
||||
// Scan dir
|
||||
self.files = match self.scan_dir(self.wrkdir.as_path()) {
|
||||
Ok(f) => f,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
Ok(())
|
||||
}
|
||||
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 file and create a FsEntry
|
||||
@@ -768,6 +828,90 @@ mod tests {
|
||||
.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]
|
||||
fn test_host_fmt_error() {
|
||||
let err: HostError = HostError::new(
|
||||
|
||||
Reference in New Issue
Block a user