mirror of
https://github.com/veeso/termscp.git
synced 2026-04-09 19:51:53 -07:00
Aws s3 support
This commit is contained in:
@@ -125,7 +125,7 @@ impl FileTransferActivity {
|
||||
Err(err) => match err.kind() {
|
||||
FileTransferErrorType::UnsupportedFeature => {
|
||||
// If copy is not supported, perform the tricky copy
|
||||
self.tricky_copy(entry, dest);
|
||||
let _ = self.tricky_copy(entry, dest);
|
||||
}
|
||||
_ => self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
@@ -143,7 +143,7 @@ impl FileTransferActivity {
|
||||
/// ### tricky_copy
|
||||
///
|
||||
/// Tricky copy will be used whenever copy command is not available on remote host
|
||||
fn tricky_copy(&mut self, entry: FsEntry, dest: &Path) {
|
||||
pub(super) fn tricky_copy(&mut self, entry: FsEntry, dest: &Path) -> Result<(), String> {
|
||||
// NOTE: VERY IMPORTANT; wait block must be umounted or something really bad will happen
|
||||
self.umount_wait();
|
||||
// match entry
|
||||
@@ -157,7 +157,7 @@ impl FileTransferActivity {
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not create temporary file: {}", err),
|
||||
);
|
||||
return;
|
||||
return Err(String::from("Could not create temporary file"));
|
||||
}
|
||||
};
|
||||
// Download file
|
||||
@@ -170,7 +170,7 @@ impl FileTransferActivity {
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not download to temporary file: {}", err),
|
||||
);
|
||||
return;
|
||||
return Err(err);
|
||||
}
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) {
|
||||
@@ -184,7 +184,7 @@ impl FileTransferActivity {
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
return Err(err.to_string());
|
||||
}
|
||||
};
|
||||
// Upload file to destination
|
||||
@@ -202,8 +202,9 @@ impl FileTransferActivity {
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
FsEntry::Directory(_) => {
|
||||
let tempdir: tempfile::TempDir = match tempfile::TempDir::new() {
|
||||
@@ -213,7 +214,7 @@ impl FileTransferActivity {
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: could not create temporary directory: {}", err),
|
||||
);
|
||||
return;
|
||||
return Err(err.to_string());
|
||||
}
|
||||
};
|
||||
// Get path of dest
|
||||
@@ -227,7 +228,7 @@ impl FileTransferActivity {
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: failed to download file: {}", err),
|
||||
);
|
||||
return;
|
||||
return Err(err);
|
||||
}
|
||||
// Stat dir
|
||||
let tempdir_entry: FsEntry = match self.host.stat(tempdir_path.as_path()) {
|
||||
@@ -241,7 +242,7 @@ impl FileTransferActivity {
|
||||
err
|
||||
),
|
||||
);
|
||||
return;
|
||||
return Err(err.to_string());
|
||||
}
|
||||
};
|
||||
// Upload to destination
|
||||
@@ -255,8 +256,9 @@ impl FileTransferActivity {
|
||||
LogLevel::Error,
|
||||
format!("Copy failed: failed to send file: {}", err),
|
||||
);
|
||||
return;
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
*/
|
||||
// locals
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel};
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -99,24 +100,29 @@ impl FileTransferActivity {
|
||||
};
|
||||
if let FsEntry::File(local_file) = local_file {
|
||||
// Create file
|
||||
match self.client.send_file(&local_file, file_path.as_path()) {
|
||||
let reader = Box::new(match File::open(tfile.path()) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not open tempfile: {}", err),
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
match self
|
||||
.client
|
||||
.send_file_wno_stream(&local_file, file_path.as_path(), reader)
|
||||
{
|
||||
Err(err) => self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!("Could not create file \"{}\": {}", file_path.display(), err),
|
||||
),
|
||||
Ok(writer) => {
|
||||
// Finalize write
|
||||
if let Err(err) = self.client.on_sent(writer) {
|
||||
self.log_and_alert(
|
||||
LogLevel::Warn,
|
||||
format!("Could not finalize file: {}", err),
|
||||
);
|
||||
} else {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!("Created file \"{}\"", file_path.display()),
|
||||
);
|
||||
}
|
||||
Ok(_) => {
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!("Created file \"{}\"", file_path.display()),
|
||||
);
|
||||
// Reload files
|
||||
self.reload_remote_dir();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
*/
|
||||
// locals
|
||||
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
|
||||
use crate::filetransfer::FileTransferErrorType;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -114,6 +115,9 @@ impl FileTransferActivity {
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(err) if err.kind() == FileTransferErrorType::UnsupportedFeature => {
|
||||
self.tricky_move(entry, dest);
|
||||
}
|
||||
Err(err) => self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
@@ -125,4 +129,41 @@ impl FileTransferActivity {
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### tricky_move
|
||||
///
|
||||
/// Tricky move will be used whenever copy command is not available on remote host.
|
||||
/// It basically uses the tricky_copy function, then it just deletes the previous entry (`entry`)
|
||||
fn tricky_move(&mut self, entry: &FsEntry, dest: &Path) {
|
||||
debug!(
|
||||
"Using tricky-move to move entry {} to {}",
|
||||
entry.get_abs_path().display(),
|
||||
dest.display()
|
||||
);
|
||||
if self.tricky_copy(entry.clone(), dest).is_ok() {
|
||||
// Delete remote existing entry
|
||||
debug!("Tricky-copy worked; removing existing remote entry");
|
||||
match self.client.remove(entry) {
|
||||
Ok(_) => self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Moved \"{}\" to \"{}\"",
|
||||
entry.get_abs_path().display(),
|
||||
dest.display()
|
||||
),
|
||||
),
|
||||
Err(err) => self.log_and_alert(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Copied \"{}\" to \"{}\"; but failed to remove src: {}",
|
||||
entry.get_abs_path().display(),
|
||||
dest.display(),
|
||||
err
|
||||
),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
error!("Tricky move aborted due to tricky-copy failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,10 @@ impl ProgressStates {
|
||||
///
|
||||
/// Calculate progress in a range between 0.0 to 1.0
|
||||
pub fn calc_progress(&self) -> f64 {
|
||||
// Prevent dividing by 0
|
||||
if self.total == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
let prog: f64 = (self.written as f64) / (self.total as f64);
|
||||
match prog > 1.0 {
|
||||
true => 1.0,
|
||||
@@ -238,6 +242,11 @@ mod test {
|
||||
// Check if terminated at started
|
||||
states.started = Instant::now();
|
||||
assert_eq!(states.calc_bytes_per_second(), 1024);
|
||||
// Divide by zero
|
||||
let states: ProgressStates = ProgressStates::default();
|
||||
assert_eq!(states.total, 0);
|
||||
assert_eq!(states.written, 0);
|
||||
assert_eq!(states.calc_progress(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
// Locals
|
||||
use super::{ConfigClient, FileTransferActivity, LogLevel, LogRecord};
|
||||
use crate::filetransfer::ProtocolParams;
|
||||
use crate::system::environment;
|
||||
use crate::system::sshkey_storage::SshKeyStorage;
|
||||
use crate::utils::path;
|
||||
@@ -134,4 +135,15 @@ impl FileTransferActivity {
|
||||
pub(super) fn remote_to_abs_path(&self, path: &Path) -> PathBuf {
|
||||
path::absolutize(self.remote().wrkdir.as_path(), path)
|
||||
}
|
||||
|
||||
/// ### get_remote_hostname
|
||||
///
|
||||
/// Get remote hostname
|
||||
pub(super) fn get_remote_hostname(&self) -> String {
|
||||
let ft_params = self.context().ft_params().unwrap();
|
||||
match &ft_params.params {
|
||||
ProtocolParams::Generic(params) => params.address.clone(),
|
||||
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,8 @@ pub(self) mod view;
|
||||
// locals
|
||||
use super::{Activity, Context, ExitReason};
|
||||
use crate::config::themes::Theme;
|
||||
use crate::filetransfer::ftp_transfer::FtpFileTransfer;
|
||||
use crate::filetransfer::scp_transfer::ScpFileTransfer;
|
||||
use crate::filetransfer::sftp_transfer::SftpFileTransfer;
|
||||
use crate::filetransfer::{FileTransfer, FileTransferProtocol};
|
||||
use crate::filetransfer::{FileTransfer, FileTransferProtocol, ProtocolParams};
|
||||
use crate::filetransfer::{FtpFileTransfer, S3FileTransfer, ScpFileTransfer, SftpFileTransfer};
|
||||
use crate::fs::explorer::FileExplorer;
|
||||
use crate::fs::FsEntry;
|
||||
use crate::host::Localhost;
|
||||
@@ -155,6 +153,7 @@ impl FileTransferActivity {
|
||||
FileTransferProtocol::Scp => {
|
||||
Box::new(ScpFileTransfer::new(Self::make_ssh_storage(&config_client)))
|
||||
}
|
||||
FileTransferProtocol::AwsS3 => Box::new(S3FileTransfer::default()),
|
||||
},
|
||||
browser: Browser::new(&config_client),
|
||||
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
||||
@@ -237,6 +236,28 @@ impl FileTransferActivity {
|
||||
fn theme(&self) -> &Theme {
|
||||
self.context().theme_provider().theme()
|
||||
}
|
||||
|
||||
/// ### get_connection_msg
|
||||
///
|
||||
/// Get connection message to show to client
|
||||
fn get_connection_msg(params: &ProtocolParams) -> String {
|
||||
match params {
|
||||
ProtocolParams::Generic(params) => {
|
||||
info!(
|
||||
"Client is not connected to remote; connecting to {}:{}",
|
||||
params.address, params.port
|
||||
);
|
||||
format!("Connecting to {}:{}…", params.address, params.port)
|
||||
}
|
||||
ProtocolParams::AwsS3(params) => {
|
||||
info!(
|
||||
"Client is not connected to remote; connecting to {} ({})",
|
||||
params.bucket_name, params.region
|
||||
);
|
||||
format!("Connecting to {}…", params.bucket_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,12 +311,9 @@ impl Activity for FileTransferActivity {
|
||||
}
|
||||
// Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error)
|
||||
if !self.client.is_connected() && self.view.get_props(COMPONENT_TEXT_FATAL).is_none() {
|
||||
let params = self.context().ft_params().unwrap();
|
||||
info!(
|
||||
"Client is not connected to remote; connecting to {}:{}",
|
||||
params.address, params.port
|
||||
);
|
||||
let msg: String = format!("Connecting to {}:{}…", params.address, params.port);
|
||||
let ftparams = self.context().ft_params().unwrap();
|
||||
// print params
|
||||
let msg: String = Self::get_connection_msg(&ftparams.params);
|
||||
// Set init state to connecting popup
|
||||
self.mount_wait(msg.as_str());
|
||||
// Force ui draw
|
||||
|
||||
@@ -34,6 +34,7 @@ use crate::utils::fmt::fmt_millis;
|
||||
|
||||
// Ext
|
||||
use bytesize::ByteSize;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
@@ -76,22 +77,20 @@ impl FileTransferActivity {
|
||||
///
|
||||
/// Connect to remote
|
||||
pub(super) fn connect(&mut self) {
|
||||
let params = self.context().ft_params().unwrap().clone();
|
||||
let addr: String = params.address.clone();
|
||||
let entry_dir: Option<PathBuf> = params.entry_directory.clone();
|
||||
let ft_params = self.context().ft_params().unwrap().clone();
|
||||
let entry_dir: Option<PathBuf> = ft_params.entry_directory.clone();
|
||||
// Connect to remote
|
||||
match self.client.connect(
|
||||
params.address,
|
||||
params.port,
|
||||
params.username,
|
||||
params.password,
|
||||
) {
|
||||
match self.client.connect(&ft_params.params) {
|
||||
Ok(welcome) => {
|
||||
if let Some(banner) = welcome {
|
||||
// Log welcome
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!("Established connection with '{}': \"{}\"", addr, banner),
|
||||
format!(
|
||||
"Established connection with '{}': \"{}\"",
|
||||
self.get_remote_hostname(),
|
||||
banner
|
||||
),
|
||||
);
|
||||
}
|
||||
// Try to change directory to entry directory
|
||||
@@ -121,8 +120,7 @@ impl FileTransferActivity {
|
||||
///
|
||||
/// disconnect from remote
|
||||
pub(super) fn disconnect(&mut self) {
|
||||
let params = self.context().ft_params().unwrap();
|
||||
let msg: String = format!("Disconnecting from {}…", params.address);
|
||||
let msg: String = format!("Disconnecting from {}…", self.get_remote_hostname());
|
||||
// Show popup disconnecting
|
||||
self.mount_wait(msg.as_str());
|
||||
// Disconnect
|
||||
@@ -442,103 +440,165 @@ impl FileTransferActivity {
|
||||
// Upload file
|
||||
// Try to open local file
|
||||
match self.host.open_file_read(local.abs_path.as_path()) {
|
||||
Ok(mut fhnd) => match self.client.send_file(local, remote) {
|
||||
Ok(mut rhnd) => {
|
||||
// Write file
|
||||
let file_size: usize =
|
||||
fhnd.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
|
||||
// Init transfer
|
||||
self.transfer.partial.init(file_size);
|
||||
// rewind
|
||||
if let Err(err) = fhnd.seek(std::io::SeekFrom::Start(0)) {
|
||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
||||
}
|
||||
// Write remote file
|
||||
let mut total_bytes_written: usize = 0;
|
||||
let mut last_progress_val: f64 = 0.0;
|
||||
let mut last_input_event_fetch: Option<Instant> = None;
|
||||
// While the entire file hasn't been completely written,
|
||||
// Or filetransfer has been aborted
|
||||
while total_bytes_written < file_size && !self.transfer.aborted() {
|
||||
// Handle input events (each 500ms) or if never fetched before
|
||||
if last_input_event_fetch.is_none()
|
||||
|| last_input_event_fetch
|
||||
.unwrap_or_else(Instant::now)
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
>= 500
|
||||
{
|
||||
// Read events
|
||||
self.read_input_event();
|
||||
// Reset instant
|
||||
last_input_event_fetch = Some(Instant::now());
|
||||
}
|
||||
// Read till you can
|
||||
let mut buffer: [u8; 65536] = [0; 65536];
|
||||
let delta: usize = match fhnd.read(&mut buffer) {
|
||||
Ok(bytes_read) => {
|
||||
total_bytes_written += bytes_read;
|
||||
if bytes_read == 0 {
|
||||
continue;
|
||||
} else {
|
||||
let mut delta: usize = 0;
|
||||
while delta < bytes_read {
|
||||
// Write bytes
|
||||
match rhnd.write(&buffer[delta..bytes_read]) {
|
||||
Ok(bytes) => {
|
||||
delta += bytes;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::RemoteIoError(
|
||||
err,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
delta
|
||||
Ok(fhnd) => match self.client.send_file(local, remote) {
|
||||
Ok(rhnd) => {
|
||||
self.filetransfer_send_one_with_stream(local, remote, file_name, fhnd, rhnd)
|
||||
}
|
||||
Err(err) if err.kind() == FileTransferErrorType::UnsupportedFeature => {
|
||||
self.filetransfer_send_one_wno_stream(local, remote, file_name, fhnd)
|
||||
}
|
||||
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
||||
},
|
||||
Err(err) => Err(TransferErrorReason::HostError(err)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### filetransfer_send_one_with_stream
|
||||
///
|
||||
/// Send file to remote using stream
|
||||
fn filetransfer_send_one_with_stream(
|
||||
&mut self,
|
||||
local: &FsFile,
|
||||
remote: &Path,
|
||||
file_name: String,
|
||||
mut reader: File,
|
||||
mut writer: Box<dyn Write>,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// Write file
|
||||
let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
|
||||
// Init transfer
|
||||
self.transfer.partial.init(file_size);
|
||||
// rewind
|
||||
if let Err(err) = reader.seek(std::io::SeekFrom::Start(0)) {
|
||||
return Err(TransferErrorReason::CouldNotRewind(err));
|
||||
}
|
||||
// Write remote file
|
||||
let mut total_bytes_written: usize = 0;
|
||||
let mut last_progress_val: f64 = 0.0;
|
||||
let mut last_input_event_fetch: Option<Instant> = None;
|
||||
// While the entire file hasn't been completely written,
|
||||
// Or filetransfer has been aborted
|
||||
while total_bytes_written < file_size && !self.transfer.aborted() {
|
||||
// Handle input events (each 500ms) or if never fetched before
|
||||
if last_input_event_fetch.is_none()
|
||||
|| last_input_event_fetch
|
||||
.unwrap_or_else(Instant::now)
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
>= 500
|
||||
{
|
||||
// Read events
|
||||
self.read_input_event();
|
||||
// Reset instant
|
||||
last_input_event_fetch = Some(Instant::now());
|
||||
}
|
||||
// Read till you can
|
||||
let mut buffer: [u8; 65536] = [0; 65536];
|
||||
let delta: usize = match reader.read(&mut buffer) {
|
||||
Ok(bytes_read) => {
|
||||
total_bytes_written += bytes_read;
|
||||
if bytes_read == 0 {
|
||||
continue;
|
||||
} else {
|
||||
let mut delta: usize = 0;
|
||||
while delta < bytes_read {
|
||||
// Write bytes
|
||||
match writer.write(&buffer[delta..bytes_read]) {
|
||||
Ok(bytes) => {
|
||||
delta += bytes;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::RemoteIoError(err));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::LocalIoError(err));
|
||||
}
|
||||
};
|
||||
// Increase progress
|
||||
self.transfer.partial.update_progress(delta);
|
||||
self.transfer.full.update_progress(delta);
|
||||
// Draw only if a significant progress has been made (performance improvement)
|
||||
if last_progress_val < self.transfer.partial.calc_progress() - 0.01 {
|
||||
// Draw
|
||||
self.update_progress_bar(format!("Uploading \"{}\"…", file_name));
|
||||
self.view();
|
||||
last_progress_val = self.transfer.partial.calc_progress();
|
||||
}
|
||||
delta
|
||||
}
|
||||
// Finalize stream
|
||||
if let Err(err) = self.client.on_sent(rhnd) {
|
||||
self.log(
|
||||
LogLevel::Warn,
|
||||
format!("Could not finalize remote stream: \"{}\"", err),
|
||||
);
|
||||
}
|
||||
// if upload was abrupted, return error
|
||||
if self.transfer.aborted() {
|
||||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||
local.abs_path.display(),
|
||||
remote.display(),
|
||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(err) => return Err(TransferErrorReason::FileTransferError(err)),
|
||||
},
|
||||
Err(err) => return Err(TransferErrorReason::HostError(err)),
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::LocalIoError(err));
|
||||
}
|
||||
};
|
||||
// Increase progress
|
||||
self.transfer.partial.update_progress(delta);
|
||||
self.transfer.full.update_progress(delta);
|
||||
// Draw only if a significant progress has been made (performance improvement)
|
||||
if last_progress_val < self.transfer.partial.calc_progress() - 0.01 {
|
||||
// Draw
|
||||
self.update_progress_bar(format!("Uploading \"{}\"…", file_name));
|
||||
self.view();
|
||||
last_progress_val = self.transfer.partial.calc_progress();
|
||||
}
|
||||
}
|
||||
// Finalize stream
|
||||
if let Err(err) = self.client.on_sent(writer) {
|
||||
self.log(
|
||||
LogLevel::Warn,
|
||||
format!("Could not finalize remote stream: \"{}\"", err),
|
||||
);
|
||||
}
|
||||
// if upload was abrupted, return error
|
||||
if self.transfer.aborted() {
|
||||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||
local.abs_path.display(),
|
||||
remote.display(),
|
||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### filetransfer_send_one_wno_stream
|
||||
///
|
||||
/// Send an `FsFile` to remote without using streams.
|
||||
fn filetransfer_send_one_wno_stream(
|
||||
&mut self,
|
||||
local: &FsFile,
|
||||
remote: &Path,
|
||||
file_name: String,
|
||||
mut reader: File,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// Write file
|
||||
let file_size: usize = reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
|
||||
// Init transfer
|
||||
self.transfer.partial.init(file_size);
|
||||
// rewind
|
||||
if let Err(err) = reader.seek(std::io::SeekFrom::Start(0)) {
|
||||
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
|
||||
.send_file_wno_stream(local, remote, Box::new(reader))
|
||||
{
|
||||
return Err(TransferErrorReason::FileTransferError(err));
|
||||
}
|
||||
// Set transfer size ok
|
||||
self.transfer.partial.update_progress(file_size);
|
||||
self.transfer.full.update_progress(file_size);
|
||||
// Draw again after
|
||||
self.update_progress_bar(format!("Uploading \"{}\"…", file_name));
|
||||
self.view();
|
||||
// log and return Ok
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||
local.abs_path.display(),
|
||||
remote.display(),
|
||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -796,120 +856,187 @@ impl FileTransferActivity {
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// Try to open local file
|
||||
match self.host.open_file_write(local) {
|
||||
Ok(mut local_file) => {
|
||||
Ok(local_file) => {
|
||||
// Download file from remote
|
||||
match self.client.recv_file(remote) {
|
||||
Ok(mut rhnd) => {
|
||||
let mut total_bytes_written: usize = 0;
|
||||
// Init transfer
|
||||
self.transfer.partial.init(remote.size);
|
||||
// Write local file
|
||||
let mut last_progress_val: f64 = 0.0;
|
||||
let mut last_input_event_fetch: Option<Instant> = None;
|
||||
// While the entire file hasn't been completely read,
|
||||
// Or filetransfer has been aborted
|
||||
while total_bytes_written < remote.size && !self.transfer.aborted() {
|
||||
// Handle input events (each 500 ms) or is None
|
||||
if last_input_event_fetch.is_none()
|
||||
|| last_input_event_fetch
|
||||
.unwrap_or_else(Instant::now)
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
>= 500
|
||||
{
|
||||
// Read events
|
||||
self.read_input_event();
|
||||
// Reset instant
|
||||
last_input_event_fetch = Some(Instant::now());
|
||||
}
|
||||
// Read till you can
|
||||
let mut buffer: [u8; 65536] = [0; 65536];
|
||||
let delta: usize = match rhnd.read(&mut buffer) {
|
||||
Ok(bytes_read) => {
|
||||
total_bytes_written += bytes_read;
|
||||
if bytes_read == 0 {
|
||||
continue;
|
||||
} else {
|
||||
let mut delta: usize = 0;
|
||||
while delta < bytes_read {
|
||||
// Write bytes
|
||||
match local_file.write(&buffer[delta..bytes_read]) {
|
||||
Ok(bytes) => delta += bytes,
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::LocalIoError(
|
||||
err,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
delta
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::RemoteIoError(err));
|
||||
}
|
||||
};
|
||||
// Set progress
|
||||
self.transfer.partial.update_progress(delta);
|
||||
self.transfer.full.update_progress(delta);
|
||||
// Draw only if a significant progress has been made (performance improvement)
|
||||
if last_progress_val < self.transfer.partial.calc_progress() - 0.01 {
|
||||
// Draw
|
||||
self.update_progress_bar(format!("Downloading \"{}\"", file_name));
|
||||
self.view();
|
||||
last_progress_val = self.transfer.partial.calc_progress();
|
||||
}
|
||||
}
|
||||
// Finalize stream
|
||||
if let Err(err) = self.client.on_recv(rhnd) {
|
||||
self.log(
|
||||
LogLevel::Warn,
|
||||
format!("Could not finalize remote stream: \"{}\"", err),
|
||||
);
|
||||
}
|
||||
// If download was abrupted, return Error
|
||||
if self.transfer.aborted() {
|
||||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
// Apply file mode to file
|
||||
#[cfg(any(
|
||||
target_family = "unix",
|
||||
target_os = "macos",
|
||||
target_os = "linux"
|
||||
))]
|
||||
if let Some((owner, group, others)) = remote.unix_pex {
|
||||
if let Err(err) = self
|
||||
.host
|
||||
.chmod(local, (owner.as_byte(), group.as_byte(), others.as_byte()))
|
||||
{
|
||||
self.log(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Could not apply file mode {:?} to \"{}\": {}",
|
||||
(owner.as_byte(), group.as_byte(), others.as_byte()),
|
||||
local.display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Log
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||
remote.abs_path.display(),
|
||||
local.display(),
|
||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||
),
|
||||
);
|
||||
Ok(rhnd) => self.filetransfer_recv_one_with_stream(
|
||||
local, remote, file_name, rhnd, local_file,
|
||||
),
|
||||
Err(err) if err.kind() == FileTransferErrorType::UnsupportedFeature => {
|
||||
self.filetransfer_recv_one_wno_stream(local, remote, file_name)
|
||||
}
|
||||
Err(err) => return Err(TransferErrorReason::FileTransferError(err)),
|
||||
Err(err) => Err(TransferErrorReason::FileTransferError(err)),
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(TransferErrorReason::HostError(err)),
|
||||
Err(err) => Err(TransferErrorReason::HostError(err)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### filetransfer_recv_one_with_stream
|
||||
///
|
||||
/// Receive an `FsEntry` from remote using stream
|
||||
fn filetransfer_recv_one_with_stream(
|
||||
&mut self,
|
||||
local: &Path,
|
||||
remote: &FsFile,
|
||||
file_name: String,
|
||||
mut reader: Box<dyn Read>,
|
||||
mut writer: File,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
let mut total_bytes_written: usize = 0;
|
||||
// Init transfer
|
||||
self.transfer.partial.init(remote.size);
|
||||
// Write local file
|
||||
let mut last_progress_val: f64 = 0.0;
|
||||
let mut last_input_event_fetch: Option<Instant> = None;
|
||||
// While the entire file hasn't been completely read,
|
||||
// Or filetransfer has been aborted
|
||||
while total_bytes_written < remote.size && !self.transfer.aborted() {
|
||||
// Handle input events (each 500 ms) or is None
|
||||
if last_input_event_fetch.is_none()
|
||||
|| last_input_event_fetch
|
||||
.unwrap_or_else(Instant::now)
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
>= 500
|
||||
{
|
||||
// Read events
|
||||
self.read_input_event();
|
||||
// Reset instant
|
||||
last_input_event_fetch = Some(Instant::now());
|
||||
}
|
||||
// Read till you can
|
||||
let mut buffer: [u8; 65536] = [0; 65536];
|
||||
let delta: usize = match reader.read(&mut buffer) {
|
||||
Ok(bytes_read) => {
|
||||
total_bytes_written += bytes_read;
|
||||
if bytes_read == 0 {
|
||||
continue;
|
||||
} else {
|
||||
let mut delta: usize = 0;
|
||||
while delta < bytes_read {
|
||||
// Write bytes
|
||||
match writer.write(&buffer[delta..bytes_read]) {
|
||||
Ok(bytes) => delta += bytes,
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::LocalIoError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
delta
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(TransferErrorReason::RemoteIoError(err));
|
||||
}
|
||||
};
|
||||
// Set progress
|
||||
self.transfer.partial.update_progress(delta);
|
||||
self.transfer.full.update_progress(delta);
|
||||
// Draw only if a significant progress has been made (performance improvement)
|
||||
if last_progress_val < self.transfer.partial.calc_progress() - 0.01 {
|
||||
// Draw
|
||||
self.update_progress_bar(format!("Downloading \"{}\"", file_name));
|
||||
self.view();
|
||||
last_progress_val = self.transfer.partial.calc_progress();
|
||||
}
|
||||
}
|
||||
// Finalize stream
|
||||
if let Err(err) = self.client.on_recv(reader) {
|
||||
self.log(
|
||||
LogLevel::Warn,
|
||||
format!("Could not finalize remote stream: \"{}\"", err),
|
||||
);
|
||||
}
|
||||
// If download was abrupted, return Error
|
||||
if self.transfer.aborted() {
|
||||
return Err(TransferErrorReason::Abrupted);
|
||||
}
|
||||
// Apply file mode to file
|
||||
#[cfg(target_family = "unix")]
|
||||
if let Some((owner, group, others)) = remote.unix_pex {
|
||||
if let Err(err) = self
|
||||
.host
|
||||
.chmod(local, (owner.as_byte(), group.as_byte(), others.as_byte()))
|
||||
{
|
||||
self.log(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Could not apply file mode {:?} to \"{}\": {}",
|
||||
(owner.as_byte(), group.as_byte(), others.as_byte()),
|
||||
local.display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Log
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||
remote.abs_path.display(),
|
||||
local.display(),
|
||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ### filetransfer_recv_one_with_stream
|
||||
///
|
||||
/// Receive an `FsEntry` from remote without using stream
|
||||
fn filetransfer_recv_one_wno_stream(
|
||||
&mut self,
|
||||
local: &Path,
|
||||
remote: &FsFile,
|
||||
file_name: String,
|
||||
) -> Result<(), TransferErrorReason> {
|
||||
// Init transfer
|
||||
self.transfer.partial.init(remote.size);
|
||||
// Draw before transfer
|
||||
self.update_progress_bar(format!("Downloading \"{}\"", file_name));
|
||||
self.view();
|
||||
// recv wno stream
|
||||
if let Err(err) = self.client.recv_file_wno_stream(remote, local) {
|
||||
return Err(TransferErrorReason::FileTransferError(err));
|
||||
}
|
||||
// Update progress at the end
|
||||
self.transfer.partial.update_progress(remote.size);
|
||||
self.transfer.full.update_progress(remote.size);
|
||||
// Draw after transfer
|
||||
self.update_progress_bar(format!("Downloading \"{}\"", file_name));
|
||||
self.view();
|
||||
// Apply file mode to file
|
||||
#[cfg(target_family = "unix")]
|
||||
if let Some((owner, group, others)) = remote.unix_pex {
|
||||
if let Err(err) = self
|
||||
.host
|
||||
.chmod(local, (owner.as_byte(), group.as_byte(), others.as_byte()))
|
||||
{
|
||||
self.log(
|
||||
LogLevel::Error,
|
||||
format!(
|
||||
"Could not apply file mode {:?} to \"{}\": {}",
|
||||
(owner.as_byte(), group.as_byte(), others.as_byte()),
|
||||
local.display(),
|
||||
err
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Log
|
||||
self.log(
|
||||
LogLevel::Info,
|
||||
format!(
|
||||
"Saved file \"{}\" to \"{}\" (took {} seconds; at {}/s)",
|
||||
remote.abs_path.display(),
|
||||
local.display(),
|
||||
fmt_millis(self.transfer.partial.started().elapsed()),
|
||||
ByteSize(self.transfer.partial.calc_bytes_per_second()),
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -810,14 +810,14 @@ impl FileTransferActivity {
|
||||
.store()
|
||||
.get_unsigned(super::STORAGE_EXPLORER_WIDTH)
|
||||
.unwrap_or(256);
|
||||
let params = self.context().ft_params().unwrap();
|
||||
let hostname = self.get_remote_hostname();
|
||||
let hostname: String = format!(
|
||||
"{}:{} ",
|
||||
params.address,
|
||||
hostname,
|
||||
fmt_path_elide_ex(
|
||||
self.remote().wrkdir.as_path(),
|
||||
width,
|
||||
params.address.len() + 3 // 3 because of '/…/'
|
||||
hostname.len() + 3 // 3 because of '/…/'
|
||||
)
|
||||
);
|
||||
let files: Vec<String> = self
|
||||
|
||||
Reference in New Issue
Block a user