mirror of
https://github.com/veeso/termscp.git
synced 2026-06-08 14:18:41 +02:00
Tricky-copy in case copy is not supported
This commit is contained in:
@@ -33,13 +33,17 @@ Released on FIXME: ??
|
|||||||
- Select a file with `<M>`, the file when selected will have a `*` prepended to its name
|
- Select a file with `<M>`, the file when selected will have a `*` prepended to its name
|
||||||
- Select all files in the current directory with `<CTRL+A>`
|
- Select all files in the current directory with `<CTRL+A>`
|
||||||
- Read more on manual: [Work on multiple files](docs/man.md#Work-on-multiple-files-)
|
- Read more on manual: [Work on multiple files](docs/man.md#Work-on-multiple-files-)
|
||||||
|
- **File transfer changes**
|
||||||
|
- *SFTP*
|
||||||
|
- Added **COPY** command to SFTP (Please note that Copy command is not supported by SFTP natively, so here it just uses the `cp` shell command as it does in SCP).
|
||||||
|
- *FTP*
|
||||||
|
- Added support for file copy (achieved through *tricky-copy*: the file is first downloaded, then uploaded with a different file name)
|
||||||
- Enhancements
|
- Enhancements
|
||||||
- Added a status bar in the file explorer showing whether the sync browser is enabled and which file sorting mode is selected
|
- Added a status bar in the file explorer showing whether the sync browser is enabled and which file sorting mode is selected
|
||||||
- Removed the goold old figlet title
|
- Removed the goold old figlet title
|
||||||
- Protocol input as first field in UI
|
- Protocol input as first field in UI
|
||||||
- Port is now updated to standard for selected protocol
|
- Port is now updated to standard for selected protocol
|
||||||
- when you change the protocol in the authentication form and the current port is standard (`< 1024`), the port will be automatically changed to default value for the selected protocol (e.g. current port: `123`, protocol is changes to `FTP`, port becomes `21`)
|
- when you change the protocol in the authentication form and the current port is standard (`< 1024`), the port will be automatically changed to default value for the selected protocol (e.g. current port: `123`, protocol is changes to `FTP`, port becomes `21`)
|
||||||
- Added **COPY** command to SFTP (Please note that Copy command is not supported by SFTP natively, so here it just uses the `cp` shell command as it does in SCP).
|
|
||||||
- Bugfix:
|
- Bugfix:
|
||||||
- Fixed wrong text wrap in log box
|
- Fixed wrong text wrap in log box
|
||||||
- Fixed error message not being shown after an upload failure
|
- Fixed error message not being shown after an upload failure
|
||||||
|
|||||||
@@ -59,11 +59,19 @@ pub struct FileTransferError {
|
|||||||
msg: Option<String>,
|
msg: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileTransferError {
|
||||||
|
/// ### kind
|
||||||
|
///
|
||||||
|
/// Returns the error kind
|
||||||
|
pub fn kind(&self) -> FileTransferErrorType {
|
||||||
|
self.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ## FileTransferErrorType
|
/// ## FileTransferErrorType
|
||||||
///
|
///
|
||||||
/// FileTransferErrorType defines the possible errors available for a file transfer
|
/// FileTransferErrorType defines the possible errors available for a file transfer
|
||||||
#[allow(dead_code)]
|
#[derive(Error, Debug, Clone, Copy, PartialEq)]
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum FileTransferErrorType {
|
pub enum FileTransferErrorType {
|
||||||
#[error("Authentication failed")]
|
#[error("Authentication failed")]
|
||||||
AuthenticationFailed,
|
AuthenticationFailed,
|
||||||
@@ -77,8 +85,6 @@ pub enum FileTransferErrorType {
|
|||||||
DirStatFailed,
|
DirStatFailed,
|
||||||
#[error("Failed to create file")]
|
#[error("Failed to create file")]
|
||||||
FileCreateDenied,
|
FileCreateDenied,
|
||||||
#[error("IO error: {0}")]
|
|
||||||
IoErr(std::io::Error),
|
|
||||||
#[error("No such file or directory")]
|
#[error("No such file or directory")]
|
||||||
NoSuchFileOrDirectory,
|
NoSuchFileOrDirectory,
|
||||||
#[error("Not enough permissions")]
|
#[error("Not enough permissions")]
|
||||||
@@ -397,13 +403,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_filetransfer_mod_error() {
|
fn test_filetransfer_mod_error() {
|
||||||
let err: FileTransferError = FileTransferError::new_ex(
|
let err: FileTransferError = FileTransferError::new_ex(
|
||||||
FileTransferErrorType::IoErr(std::io::Error::from(std::io::ErrorKind::AddrInUse)),
|
FileTransferErrorType::NoSuchFileOrDirectory,
|
||||||
String::from("non va una mazza"),
|
String::from("non va una mazza"),
|
||||||
);
|
);
|
||||||
assert_eq!(*err.msg.as_ref().unwrap(), String::from("non va una mazza"));
|
assert_eq!(*err.msg.as_ref().unwrap(), String::from("non va una mazza"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{}", err),
|
format!("{}", err),
|
||||||
String::from("IO error: address in use (non va una mazza)")
|
String::from("No such file or directory (non va una mazza)")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!(
|
format!(
|
||||||
@@ -482,5 +488,7 @@ mod tests {
|
|||||||
),
|
),
|
||||||
String::from("Unsupported feature")
|
String::from("Unsupported feature")
|
||||||
);
|
);
|
||||||
|
let err = FileTransferError::new(FileTransferErrorType::UnsupportedFeature);
|
||||||
|
assert_eq!(err.kind(), FileTransferErrorType::UnsupportedFeature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,10 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
extern crate tempfile;
|
||||||
// locals
|
// locals
|
||||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
|
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
|
||||||
|
use crate::filetransfer::FileTransferErrorType;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
impl FileTransferActivity {
|
impl FileTransferActivity {
|
||||||
@@ -120,15 +122,21 @@ impl FileTransferActivity {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(err) => self.log_and_alert(
|
Err(err) => match err.kind() {
|
||||||
LogLevel::Error,
|
FileTransferErrorType::UnsupportedFeature => {
|
||||||
format!(
|
// If copy is not supported, perform the tricky copy
|
||||||
"Could not copy \"{}\" to \"{}\": {}",
|
self.tricky_copy(entry, dest);
|
||||||
entry.get_abs_path().display(),
|
}
|
||||||
dest.display(),
|
_ => self.log_and_alert(
|
||||||
err
|
LogLevel::Error,
|
||||||
|
format!(
|
||||||
|
"Could not copy \"{}\" to \"{}\": {}",
|
||||||
|
entry.get_abs_path().display(),
|
||||||
|
dest.display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -899,4 +899,110 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### tricky_copy
|
||||||
|
///
|
||||||
|
/// Tricky copy will be used whenever copy command is not available on remote host
|
||||||
|
pub(super) fn tricky_copy(&mut self, entry: &FsEntry, dest: &Path) {
|
||||||
|
// match entry
|
||||||
|
match entry {
|
||||||
|
FsEntry::File(entry) => {
|
||||||
|
// Create tempfile
|
||||||
|
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(err) => {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Copy failed: could not create temporary file: {}", err),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Download file
|
||||||
|
if let Err(err) =
|
||||||
|
self.filetransfer_recv_file(tmpfile.path(), entry, entry.name.clone())
|
||||||
|
{
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Copy failed: could not download to temporary file: {}", err),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get local fs entry
|
||||||
|
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!(
|
||||||
|
"Copy failed: could not stat \"{}\": {}",
|
||||||
|
tmpfile.path().display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tmpfile_entry = match &tmpfile_entry {
|
||||||
|
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
|
||||||
|
FsEntry::File(f) => f,
|
||||||
|
};
|
||||||
|
// Upload file to destination
|
||||||
|
if let Err(err) = self.filetransfer_send_file(
|
||||||
|
tmpfile_entry,
|
||||||
|
dest,
|
||||||
|
String::from(dest.to_string_lossy()),
|
||||||
|
) {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!(
|
||||||
|
"Copy failed: could not write file {}: {}",
|
||||||
|
entry.abs_path.display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FsEntry::Directory(_) => {
|
||||||
|
let tempdir: tempfile::TempDir = match tempfile::TempDir::new() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(err) => {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!("Copy failed: could not create temporary directory: {}", err),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Download file
|
||||||
|
self.filetransfer_recv(entry, tempdir.path(), None);
|
||||||
|
// Get path of dest
|
||||||
|
let mut tempdir_path: PathBuf = tempdir.path().to_path_buf();
|
||||||
|
tempdir_path.push(entry.get_name());
|
||||||
|
// Stat dir
|
||||||
|
let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Error,
|
||||||
|
format!(
|
||||||
|
"Copy failed: could not stat \"{}\": {}",
|
||||||
|
tempdir.path().display(),
|
||||||
|
err
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Upload to destination
|
||||||
|
let wrkdir: PathBuf = self.remote().wrkdir.clone();
|
||||||
|
self.filetransfer_send(
|
||||||
|
&tempdir_entry,
|
||||||
|
wrkdir.as_path(),
|
||||||
|
Some(String::from(dest.to_string_lossy())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ impl FileTransferActivity {
|
|||||||
Box::new(Input::new(
|
Box::new(Input::new(
|
||||||
InputPropsBuilder::default()
|
InputPropsBuilder::default()
|
||||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
||||||
.with_label(String::from("Insert destination name"))
|
.with_label(String::from("Copy file(s) to..."))
|
||||||
.build(),
|
.build(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@@ -586,7 +586,7 @@ impl FileTransferActivity {
|
|||||||
Box::new(Input::new(
|
Box::new(Input::new(
|
||||||
InputPropsBuilder::default()
|
InputPropsBuilder::default()
|
||||||
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
|
||||||
.with_label(String::from("Insert new name"))
|
.with_label(String::from("Move file(s) to..."))
|
||||||
.build(),
|
.build(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user