Merge pull request #42 from veeso/issue-37-progress-bar-not-visible-when-editing-remote-files

Fixed progress bar not visible when editing remote files
This commit is contained in:
Christian Visintin 2021-06-12 15:33:11 +02:00 committed by GitHub
commit 59eb7f7935
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 319 additions and 254 deletions

View file

@ -24,7 +24,8 @@ Released on FIXME: ??
- Bugfix:
- Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2)
- Help panels as `ScrollTable` to allow displaying entire content on small screens
- Fixed [Issue 39](https://github.com/veeso/termscp/issues/39): Help panels as `ScrollTable` to allow displaying entire content on small screens
- Fixed [Issue 37](https://github.com/veeso/termscp/issues/37): progress bar not visible when editing remote files
- Dependencies:
- Updated `textwrap` to `0.14.0`
- Updated `tui-realm` to `0.4.2`

View file

@ -139,4 +139,111 @@ 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) {
// 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_one(entry, tmpfile.path(), 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
let wrkdir = self.remote().wrkdir.clone();
if let Err(err) = self.filetransfer_send_one(
tmpfile_entry,
wrkdir.as_path(),
Some(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())),
);
}
}
}
}

View file

@ -27,6 +27,13 @@
*/
// locals
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
use crate::fs::FsFile;
// ext
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::fs::OpenOptions;
use std::io::Read;
use std::path::Path;
use std::time::SystemTime;
impl FileTransferActivity {
pub(crate) fn action_edit_local_file(&mut self) {
@ -76,4 +83,149 @@ impl FileTransferActivity {
// Reload entries
self.reload_remote_dir();
}
/// ### edit_local_file
///
/// Edit a file on localhost
fn edit_local_file(&mut self, path: &Path) -> Result<(), String> {
// Read first 2048 bytes or less from file to check if it is textual
match OpenOptions::new().read(true).open(path) {
Ok(mut f) => {
// Read
let mut buff: [u8; 2048] = [0; 2048];
match f.read(&mut buff) {
Ok(size) => {
if content_inspector::inspect(&buff[0..size]).is_binary() {
return Err("Could not open file in editor: file is binary".to_string());
}
}
Err(err) => {
return Err(format!("Could not read file: {}", err));
}
}
}
Err(err) => {
return Err(format!("Could not read file: {}", err));
}
}
// Put input mode back to normal
if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
// Leave alternate mode
if let Some(ctx) = self.context.as_mut() {
ctx.leave_alternate_screen();
}
// Open editor
match edit::edit_file(path) {
Ok(_) => self.log(
LogLevel::Info,
format!(
"Changes performed through editor saved to \"{}\"!",
path.display()
),
),
Err(err) => return Err(format!("Could not open editor: {}", err)),
}
if let Some(ctx) = self.context.as_mut() {
// Clear screen
ctx.clear_screen();
// Enter alternate mode
ctx.enter_alternate_screen();
}
// Re-enable raw mode
let _ = enable_raw_mode();
Ok(())
}
/// ### edit_remote_file
///
/// Edit file on remote host
fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> {
// Create temp file
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
Ok(f) => f,
Err(err) => {
return Err(format!("Could not create temporary file: {}", err));
}
};
// Download file
if let Err(err) = self.filetransfer_recv_one(file, tmpfile.path(), file.name.clone()) {
return Err(format!("Could not open file {}: {}", file.name, err));
}
// Get current file modification time
let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) {
Ok(e) => e.get_last_change_time(),
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
err
))
}
};
// Edit file
if let Err(err) = self.edit_local_file(tmpfile.path()) {
return Err(err);
}
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
err
))
}
};
// Check if file has changed
match prev_mtime != tmpfile_entry.get_last_change_time() {
true => {
self.log(
LogLevel::Info,
format!(
"File \"{}\" has changed; writing changes to remote",
file.abs_path.display()
),
);
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
err
))
}
};
// Write file
let tmpfile_entry: &FsFile = match &tmpfile_entry {
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
FsEntry::File(f) => f,
};
// Send file
let wrkdir = self.remote().wrkdir.clone();
if let Err(err) = self.filetransfer_send_one(
tmpfile_entry,
wrkdir.as_path(),
Some(file.name.clone()),
) {
return Err(format!(
"Could not write file {}: {}",
file.abs_path.display(),
err
));
}
}
false => {
self.log(
LogLevel::Info,
format!("File \"{}\" hasn't changed", file.abs_path.display()),
);
}
}
Ok(())
}
}

View file

@ -40,11 +40,9 @@ use crate::utils::fmt::fmt_millis;
// Ext
use bytesize::ByteSize;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::fs::OpenOptions;
use std::io::{Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::time::{Instant, SystemTime};
use std::time::Instant;
use thiserror::Error;
/// ## TransferErrorReason
@ -174,6 +172,38 @@ impl FileTransferActivity {
self.umount_progress_bar();
}
/// ### filetransfer_send_one
///
/// Send one file to remote at specified path.
pub(super) fn filetransfer_send_one(
&mut self,
file: &FsFile,
curr_remote_path: &Path,
dst_name: Option<String>,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = file.size;
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Uploading {}...", file.abs_path.display()));
// Get remote path
let file_name: String = file.name.clone();
let mut remote_path: PathBuf = PathBuf::from(curr_remote_path);
let remote_file_name: PathBuf = match dst_name {
Some(s) => PathBuf::from(s.as_str()),
None => PathBuf::from(file_name.as_str()),
};
remote_path.push(remote_file_name);
// Send
let result = self.filetransfer_send_file(file, remote_path.as_path(), file_name);
// Umount progress bar
self.umount_progress_bar();
// Return result
result.map_err(|x| x.to_string())
}
fn filetransfer_send_recurse(
&mut self,
entry: &FsEntry,
@ -426,6 +456,31 @@ impl FileTransferActivity {
self.umount_progress_bar();
}
/// ### filetransfer_recv_one
///
/// Receive a single file from remote.
/// Use this function instead of `filetransfer_recv_file` from external files
pub(super) fn filetransfer_recv_one(
&mut self,
entry: &FsFile,
local_path: &Path,
dst_name: String,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total transfer size
let total_transfer_size: usize = entry.size;
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Downloading {}...", entry.abs_path.display()));
// Receive
let result = self.filetransfer_recv_file(local_path, entry, dst_name);
// Umount progress bar
self.umount_progress_bar();
// Return result
result.map_err(|x| x.to_string())
}
fn filetransfer_recv_recurse(
&mut self,
entry: &FsEntry,
@ -785,256 +840,6 @@ impl FileTransferActivity {
}
}
/// ### edit_local_file
///
/// Edit a file on localhost
pub(super) fn edit_local_file(&mut self, path: &Path) -> Result<(), String> {
// Read first 2048 bytes or less from file to check if it is textual
match OpenOptions::new().read(true).open(path) {
Ok(mut f) => {
// Read
let mut buff: [u8; 2048] = [0; 2048];
match f.read(&mut buff) {
Ok(size) => {
if content_inspector::inspect(&buff[0..size]).is_binary() {
return Err("Could not open file in editor: file is binary".to_string());
}
}
Err(err) => {
return Err(format!("Could not read file: {}", err));
}
}
}
Err(err) => {
return Err(format!("Could not read file: {}", err));
}
}
// Put input mode back to normal
if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
// Leave alternate mode
if let Some(ctx) = self.context.as_mut() {
ctx.leave_alternate_screen();
}
// Open editor
match edit::edit_file(path) {
Ok(_) => self.log(
LogLevel::Info,
format!(
"Changes performed through editor saved to \"{}\"!",
path.display()
),
),
Err(err) => return Err(format!("Could not open editor: {}", err)),
}
if let Some(ctx) = self.context.as_mut() {
// Clear screen
ctx.clear_screen();
// Enter alternate mode
ctx.enter_alternate_screen();
}
// Re-enable raw mode
let _ = enable_raw_mode();
Ok(())
}
/// ### edit_remote_file
///
/// Edit file on remote host
pub(super) fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> {
// Create temp file
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
Ok(f) => f,
Err(err) => {
return Err(format!("Could not create temporary file: {}", err));
}
};
// Download file
if let Err(err) = self.filetransfer_recv_file(tmpfile.path(), file, file.name.clone()) {
return Err(format!("Could not open file {}: {}", file.name, err));
}
// Get current file modification time
let prev_mtime: SystemTime = match self.host.stat(tmpfile.path()) {
Ok(e) => e.get_last_change_time(),
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
err
))
}
};
// Edit file
if let Err(err) = self.edit_local_file(tmpfile.path()) {
return Err(err);
}
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
err
))
}
};
// Check if file has changed
match prev_mtime != tmpfile_entry.get_last_change_time() {
true => {
self.log(
LogLevel::Info,
format!(
"File \"{}\" has changed; writing changes to remote",
file.abs_path.display()
),
);
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
err
))
}
};
// Write file
let tmpfile_entry: &FsFile = match &tmpfile_entry {
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
FsEntry::File(f) => f,
};
// Send file
if let Err(err) = self.filetransfer_send_file(
tmpfile_entry,
file.abs_path.as_path(),
file.name.clone(),
) {
return Err(format!(
"Could not write file {}: {}",
file.abs_path.display(),
err
));
}
}
false => {
self.log(
LogLevel::Info,
format!("File \"{}\" hasn't changed", file.abs_path.display()),
);
}
}
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())),
);
}
}
}
// -- transfer sizes
/// ### get_total_transfer_size_local