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_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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
180
src/host/mod.rs
180
src/host/mod.rs
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user