Merge branch '0.5.1' into 0.6.0

This commit is contained in:
veeso
2021-06-21 18:22:19 +02:00
55 changed files with 2384 additions and 1639 deletions

View File

@@ -27,8 +27,9 @@
*/
extern crate tempfile;
// locals
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
use crate::filetransfer::FileTransferErrorType;
use crate::fs::FsFile;
use std::path::{Path, PathBuf};
impl FileTransferActivity {
@@ -66,7 +67,7 @@ impl FileTransferActivity {
match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => {
let dest_path: PathBuf = PathBuf::from(input);
self.remote_copy_file(&entry, dest_path.as_path());
self.remote_copy_file(entry, dest_path.as_path());
// Reload entries
self.reload_remote_dir();
}
@@ -74,7 +75,7 @@ impl FileTransferActivity {
// Try to copy each file to Input/{FILE_NAME}
let base_path: PathBuf = PathBuf::from(input);
// Iter files
for entry in entries.iter() {
for entry in entries.into_iter() {
let mut dest_path: PathBuf = base_path.clone();
dest_path.push(entry.get_name());
self.remote_copy_file(entry, dest_path.as_path());
@@ -110,8 +111,8 @@ impl FileTransferActivity {
}
}
fn remote_copy_file(&mut self, entry: &FsEntry, dest: &Path) {
match self.client.as_mut().copy(entry, dest) {
fn remote_copy_file(&mut self, entry: FsEntry, dest: &Path) {
match self.client.as_mut().copy(&entry, dest) {
Ok(_) => {
self.log(
LogLevel::Info,
@@ -139,4 +140,123 @@ 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
let name = entry.name.clone();
let entry_path = entry.abs_path.clone();
if let Err(err) =
self.filetransfer_recv(TransferPayload::File(entry), tmpfile.path(), Some(name))
{
self.log_and_alert(
LogLevel::Error,
format!("Copy failed: could not download to temporary file: {}", err),
);
return;
}
// Get local fs entry
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.path()) {
Ok(e) => e.unwrap_file(),
Err(err) => {
self.log_and_alert(
LogLevel::Error,
format!(
"Copy failed: could not stat \"{}\": {}",
tmpfile.path().display(),
err
),
);
return;
}
};
// Upload file to destination
let wrkdir = self.remote().wrkdir.clone();
if let Err(err) = self.filetransfer_send(
TransferPayload::File(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_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;
}
};
// Get path of dest
let mut tempdir_path: PathBuf = tempdir.path().to_path_buf();
tempdir_path.push(entry.get_name());
// Download file
if let Err(err) =
self.filetransfer_recv(TransferPayload::Any(entry), tempdir.path(), None)
{
self.log_and_alert(
LogLevel::Error,
format!("Copy failed: failed to download file: {}", err),
);
return;
}
// 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();
if let Err(err) = self.filetransfer_send(
TransferPayload::Any(tempdir_entry),
wrkdir.as_path(),
Some(String::from(dest.to_string_lossy())),
) {
self.log_and_alert(
LogLevel::Error,
format!("Copy failed: failed to send file: {}", err),
);
return;
}
}
}
}
}

View File

@@ -26,7 +26,14 @@
* SOFTWARE.
*/
// locals
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
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, PathBuf};
use std::time::SystemTime;
impl FileTransferActivity {
pub(crate) fn action_edit_local_file(&mut self) {
@@ -60,15 +67,15 @@ impl FileTransferActivity {
SelectedEntry::None => vec![],
};
// Edit all entries
for entry in entries.iter() {
for entry in entries.into_iter() {
// Check if file
if let FsEntry::File(file) = entry {
self.log(
LogLevel::Info,
format!("Opening file \"{}\"...", entry.get_abs_path().display()),
format!("Opening file \"{}\"...", file.abs_path.display()),
);
// Edit file
if let Err(err) = self.edit_remote_file(&file) {
if let Err(err) = self.edit_remote_file(file) {
self.log_and_alert(LogLevel::Error, err);
}
}
@@ -76,4 +83,150 @@ 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
#[cfg(not(target_os = "windows"))]
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)),
}
#[cfg(not(target_os = "windows"))]
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: PathBuf = match self.download_file_as_temp(&file) {
Ok(p) => p,
Err(err) => return Err(err),
};
// Download file
let file_name = file.name.clone();
let file_path = file.abs_path.clone();
if let Err(err) = self.filetransfer_recv(
TransferPayload::File(file),
tmpfile.as_path(),
Some(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.as_path()) {
Ok(e) => e.get_last_change_time(),
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.as_path().display(),
err
))
}
};
// Edit file
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
return Err(err);
}
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.as_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_path.display()
),
);
// Get local fs entry
let tmpfile_entry: FsFile = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e.unwrap_file(),
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.as_path().display(),
err
))
}
};
// Send file
let wrkdir = self.remote().wrkdir.clone();
if let Err(err) = self.filetransfer_send(
TransferPayload::File(tmpfile_entry),
wrkdir.as_path(),
Some(file_name),
) {
return Err(format!(
"Could not write file {}: {}",
file_path.display(),
err
));
}
}
false => {
self.log(
LogLevel::Info,
format!("File \"{}\" hasn't changed", file_path.display()),
);
}
}
Ok(())
}
}

View File

@@ -27,7 +27,7 @@
*/
// locals
use super::super::browser::FileExplorerTab;
use super::{FileTransferActivity, FsEntry, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
use std::path::PathBuf;
@@ -77,10 +77,30 @@ impl FileTransferActivity {
match self.get_found_selected_entries() {
SelectedEntry::One(entry) => match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_send(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_recv(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
},
SelectedEntry::Many(entries) => {
@@ -90,21 +110,34 @@ impl FileTransferActivity {
dest_path.push(save_as);
}
// Iter files
for entry in entries.iter() {
match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
self.filetransfer_send(
&entry.get_realfile(),
dest_path.as_path(),
None,
);
let entries = entries.iter().map(|x| x.get_realfile()).collect();
match self.browser.tab() {
FileExplorerTab::FindLocal | FileExplorerTab::Local => {
if let Err(err) = self.filetransfer_send(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
self.filetransfer_recv(
&entry.get_realfile(),
dest_path.as_path(),
None,
}
FileExplorerTab::FindRemote | FileExplorerTab::Remote => {
if let Err(err) = self.filetransfer_recv(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}

View File

@@ -25,7 +25,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel};
pub(self) use super::{FileTransferActivity, FsEntry, LogLevel, TransferPayload};
use tuirealm::{Payload, Value};
// actions
@@ -82,8 +82,7 @@ impl FileTransferActivity {
let files: Vec<&FsEntry> = files
.iter()
.map(|x| self.local().get(*x)) // Usize to Option<FsEntry>
.filter(|x| x.is_some()) // Get only some values
.map(|x| x.unwrap()) // Option to FsEntry
.flatten()
.collect();
SelectedEntry::from(files)
}
@@ -101,8 +100,7 @@ impl FileTransferActivity {
let files: Vec<&FsEntry> = files
.iter()
.map(|x| self.remote().get(*x)) // Usize to Option<FsEntry>
.filter(|x| x.is_some()) // Get only some values
.map(|x| x.unwrap()) // Option to FsEntry
.flatten()
.collect();
SelectedEntry::from(files)
}
@@ -122,8 +120,7 @@ impl FileTransferActivity {
let files: Vec<&FsEntry> = files
.iter()
.map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option<FsEntry>
.filter(|x| x.is_some()) // Get only some values
.map(|x| x.unwrap()) // Option to FsEntry
.flatten()
.collect();
SelectedEntry::from(files)
}

View File

@@ -28,7 +28,7 @@
// deps
extern crate open;
// locals
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry};
use super::{FileTransferActivity, FsEntry, LogLevel, SelectedEntry, TransferPayload};
// ext
use std::path::{Path, PathBuf};
@@ -90,12 +90,25 @@ impl FileTransferActivity {
}
Some(p) => p.path().to_path_buf(),
};
self.filetransfer_recv(&entry, cache.as_path(), Some(tmpfile.clone()));
// Make file and open if file exists
let mut tmp: PathBuf = cache;
tmp.push(tmpfile.as_str());
if tmp.exists() {
self.open_path_with(tmp.as_path(), open_with);
match self.filetransfer_recv(
TransferPayload::Any(entry),
cache.as_path(),
Some(tmpfile.clone()),
) {
Ok(_) => {
// Make file and open if file exists
let mut tmp: PathBuf = cache;
tmp.push(tmpfile.as_str());
if tmp.exists() {
self.open_path_with(tmp.as_path(), open_with);
}
}
Err(err) => {
self.log(
LogLevel::Error,
format!("Failed to download remote entry: {}", err),
);
}
}
}

View File

@@ -26,7 +26,7 @@
* SOFTWARE.
*/
// locals
use super::{FileTransferActivity, SelectedEntry};
use super::{FileTransferActivity, LogLevel, SelectedEntry, TransferPayload};
use std::path::PathBuf;
impl FileTransferActivity {
@@ -50,7 +50,19 @@ impl FileTransferActivity {
let wrkdir: PathBuf = self.remote().wrkdir.clone();
match self.get_local_selected_entries() {
SelectedEntry::One(entry) => {
self.filetransfer_send(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_send(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
SelectedEntry::Many(entries) => {
// In case of selection: save multiple files in wrkdir/input
@@ -59,8 +71,19 @@ impl FileTransferActivity {
dest_path.push(save_as);
}
// Iter files
for entry in entries.iter() {
self.filetransfer_send(&entry.get_realfile(), dest_path.as_path(), None);
let entries = entries.iter().map(|x| x.get_realfile()).collect();
if let Err(err) = self.filetransfer_send(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not upload file: {}", err),
);
return;
}
}
}
SelectedEntry::None => {}
@@ -71,7 +94,19 @@ impl FileTransferActivity {
let wrkdir: PathBuf = self.local().wrkdir.clone();
match self.get_remote_selected_entries() {
SelectedEntry::One(entry) => {
self.filetransfer_recv(&entry.get_realfile(), wrkdir.as_path(), save_as);
if let Err(err) = self.filetransfer_recv(
TransferPayload::Any(entry.get_realfile()),
wrkdir.as_path(),
save_as,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}
SelectedEntry::Many(entries) => {
// In case of selection: save multiple files in wrkdir/input
@@ -80,8 +115,19 @@ impl FileTransferActivity {
dest_path.push(save_as);
}
// Iter files
for entry in entries.iter() {
self.filetransfer_recv(&entry.get_realfile(), dest_path.as_path(), None);
let entries = entries.iter().map(|x| x.get_realfile()).collect();
if let Err(err) = self.filetransfer_recv(
TransferPayload::Many(entries),
dest_path.as_path(),
None,
) {
{
self.log_and_alert(
LogLevel::Error,
format!("Could not download file: {}", err),
);
return;
}
}
}
SelectedEntry::None => {}

View File

@@ -49,9 +49,10 @@ use crate::fs::explorer::FileExplorer;
use crate::fs::FsEntry;
use crate::host::Localhost;
use crate::system::config_client::ConfigClient;
pub(crate) use lib::browser;
pub(self) use lib::browser;
use lib::browser::Browser;
use lib::transfer::TransferStates;
pub(self) use session::TransferPayload;
// Includes
use chrono::{DateTime, Local};
@@ -89,7 +90,8 @@ const COMPONENT_RADIO_DELETE: &str = "RADIO_DELETE";
const COMPONENT_RADIO_DISCONNECT: &str = "RADIO_DISCONNECT";
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
const COMPONENT_RADIO_SORTING: &str = "RADIO_SORTING";
const COMPONENT_SPAN_STATUS_BAR: &str = "STATUS_BAR";
const COMPONENT_SPAN_STATUS_BAR_LOCAL: &str = "STATUS_BAR_LOCAL";
const COMPONENT_SPAN_STATUS_BAR_REMOTE: &str = "STATUS_BAR_REMOTE";
const COMPONENT_LIST_FILEINFO: &str = "LIST_FILEINFO";
/// ## LogLevel

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
@@ -66,6 +64,19 @@ enum TransferErrorReason {
FileTransferError(FileTransferError),
}
/// ## TransferPayload
///
/// Represents the entity to send or receive during a transfer.
/// - File: describes an individual `FsFile` to send
/// - Any: Can be any kind of `FsEntry`, but just one
/// - Many: a list of `FsEntry`
#[derive(Debug)]
pub(super) enum TransferPayload {
File(FsFile),
Any(FsEntry),
Many(Vec<FsEntry>),
}
impl FileTransferActivity {
/// ### connect
///
@@ -106,6 +117,7 @@ impl FileTransferActivity {
}
Err(err) => {
// Set popup fatal error
self.umount_wait();
self.mount_fatal(&err.to_string());
}
}
@@ -196,11 +208,66 @@ impl FileTransferActivity {
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
pub(super) fn filetransfer_send(
&mut self,
payload: TransferPayload,
curr_remote_path: &Path,
dst_name: Option<String>,
) -> Result<(), String> {
// Use different method based on payload
match payload {
TransferPayload::Any(entry) => {
self.filetransfer_send_any(&entry, curr_remote_path, dst_name)
}
TransferPayload::File(file) => {
self.filetransfer_send_file(&file, curr_remote_path, dst_name)
}
TransferPayload::Many(entries) => {
self.filetransfer_send_many(entries, curr_remote_path)
}
}
}
/// ### filetransfer_send_file
///
/// Send one file to remote at specified path.
fn filetransfer_send_file(
&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_one(file, remote_path.as_path(), file_name);
// Umount progress bar
self.umount_progress_bar();
// Return result
result.map_err(|x| x.to_string())
}
/// ### filetransfer_send_any
///
/// Send a `TransferPayload` of type `Any`
fn filetransfer_send_any(
&mut self,
entry: &FsEntry,
curr_remote_path: &Path,
dst_name: Option<String>,
) {
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
@@ -212,6 +279,34 @@ impl FileTransferActivity {
self.filetransfer_send_recurse(entry, curr_remote_path, dst_name);
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
/// ### filetransfer_send_many
///
/// Send many entries to remote
fn filetransfer_send_many(
&mut self,
entries: Vec<FsEntry>,
curr_remote_path: &Path,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = entries
.iter()
.map(|x| self.get_total_transfer_size_local(x))
.sum();
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Uploading {} entries...", entries.len()));
// Send recurse
entries
.iter()
.for_each(|x| self.filetransfer_send_recurse(x, curr_remote_path, None));
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
fn filetransfer_send_recurse(
@@ -235,8 +330,7 @@ impl FileTransferActivity {
// Match entry
match entry {
FsEntry::File(file) => {
if let Err(err) =
self.filetransfer_send_file(file, remote_path.as_path(), file_name)
if let Err(err) = self.filetransfer_send_one(file, remote_path.as_path(), file_name)
{
// Log error
self.log_and_alert(
@@ -339,7 +433,7 @@ impl FileTransferActivity {
/// ### filetransfer_send_file
///
/// Send local file and write it to remote path
fn filetransfer_send_file(
fn filetransfer_send_one(
&mut self,
local: &FsFile,
remote: &Path,
@@ -448,11 +542,29 @@ impl FileTransferActivity {
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
pub(super) fn filetransfer_recv(
&mut self,
payload: TransferPayload,
local_path: &Path,
dst_name: Option<String>,
) -> Result<(), String> {
match payload {
TransferPayload::Any(entry) => self.filetransfer_recv_any(&entry, local_path, dst_name),
TransferPayload::File(file) => self.filetransfer_recv_file(&file, local_path),
TransferPayload::Many(entries) => self.filetransfer_recv_many(entries, local_path),
}
}
/// ### filetransfer_recv_any
///
/// Recv fs entry from remote.
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
fn filetransfer_recv_any(
&mut self,
entry: &FsEntry,
local_path: &Path,
dst_name: Option<String>,
) {
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total transfer size
@@ -464,6 +576,53 @@ impl FileTransferActivity {
self.filetransfer_recv_recurse(entry, local_path, dst_name);
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
/// ### filetransfer_recv_file
///
/// Receive a single file from remote.
fn filetransfer_recv_file(&mut self, entry: &FsFile, local_path: &Path) -> 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_one(local_path, entry, entry.name.clone());
// Umount progress bar
self.umount_progress_bar();
// Return result
result.map_err(|x| x.to_string())
}
/// ### filetransfer_send_many
///
/// Send many entries to remote
fn filetransfer_recv_many(
&mut self,
entries: Vec<FsEntry>,
curr_remote_path: &Path,
) -> Result<(), String> {
// Reset states
self.transfer.reset();
// Calculate total size of transfer
let total_transfer_size: usize = entries
.iter()
.map(|x| self.get_total_transfer_size_remote(x))
.sum();
self.transfer.full.init(total_transfer_size);
// Mount progress bar
self.mount_progress_bar(format!("Downloading {} entries...", entries.len()));
// Send recurse
entries
.iter()
.for_each(|x| self.filetransfer_recv_recurse(x, curr_remote_path, None));
// Umount progress bar
self.umount_progress_bar();
Ok(())
}
fn filetransfer_recv_recurse(
@@ -489,7 +648,7 @@ impl FileTransferActivity {
local_file_path.push(local_file_name.as_str());
// Download file
if let Err(err) =
self.filetransfer_recv_file(local_file_path.as_path(), file, file_name)
self.filetransfer_recv_one(local_file_path.as_path(), file, file_name)
{
self.log_and_alert(
LogLevel::Error,
@@ -537,7 +696,11 @@ impl FileTransferActivity {
match self.host.mkdir_ex(local_dir_path.as_path(), true) {
Ok(_) => {
// Apply file mode to directory
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[cfg(any(
target_family = "unix",
target_os = "macos",
target_os = "linux"
))]
if let Some(pex) = dir.unix_pex {
if let Err(err) = self.host.chmod(local_dir_path.as_path(), pex) {
self.log(
@@ -613,10 +776,10 @@ impl FileTransferActivity {
}
}
/// ### filetransfer_recv_file
/// ### filetransfer_recv_one
///
/// Receive file from remote and write it to local path
fn filetransfer_recv_file(
fn filetransfer_recv_one(
&mut self,
local: &Path,
remote: &FsFile,
@@ -694,7 +857,11 @@ impl FileTransferActivity {
return Err(TransferErrorReason::Abrupted);
}
// Apply file mode to file
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[cfg(any(
target_family = "unix",
target_os = "macos",
target_os = "linux"
))]
if let Some(pex) = remote.unix_pex {
if let Err(err) = self.host.chmod(local, pex) {
self.log(
@@ -785,251 +952,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));
}
}
debug!("Ok, file {} is textual; opening file...", path.display());
// 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: PathBuf = match self.download_file_as_temp(file) {
Ok(p) => p,
Err(err) => return Err(err),
};
// Get current file modification time
let prev_mtime: SystemTime = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e.get_last_change_time(),
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.as_path().display(),
err
))
}
};
// Edit file
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
return Err(err);
}
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.as_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.as_path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.as_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())),
);
}
}
}
/// ### download_file_as_temp
///
/// Download provided file as a temporary file
@@ -1047,7 +969,11 @@ impl FileTransferActivity {
}
};
// Download file
match self.filetransfer_recv_file(tmpfile.as_path(), file, file.name.clone()) {
match self.filetransfer_recv(
TransferPayload::File(file.clone()),
tmpfile.as_path(),
Some(file.name.clone()),
) {
Err(err) => Err(format!(
"Could not download {} to temporary file: {}",
file.abs_path.display(),

View File

@@ -107,6 +107,8 @@ impl Update for FileTransferActivity {
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => {
// Toggle hidden files
self.local_mut().toggle_hidden_files();
// Update status bar
self.refresh_local_status_bar();
// Reload file list component
self.update_local_filelist()
}
@@ -179,6 +181,8 @@ impl Update for FileTransferActivity {
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => {
// Toggle hidden files
self.remote_mut().toggle_hidden_files();
// Update status bar
self.refresh_remote_status_bar();
// Reload file list component
self.update_remote_filelist()
}
@@ -295,7 +299,7 @@ impl Update for FileTransferActivity {
// Toggle browser sync
self.browser.toggle_sync_browsing();
// Update status bar
self.refresh_status_bar();
self.refresh_remote_status_bar();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC)
@@ -652,7 +656,11 @@ impl Update for FileTransferActivity {
_ => panic!("Found result doesn't support SORTING"),
}
// Update status bar
self.refresh_status_bar();
match self.browser.tab() {
FileExplorerTab::Local => self.refresh_local_status_bar(),
FileExplorerTab::Remote => self.refresh_remote_status_bar(),
_ => panic!("Found result doesn't support SORTING"),
};
// Reload files
match self.browser.tab() {
FileExplorerTab::Local => self.update_local_filelist(),

View File

@@ -28,7 +28,7 @@
// Deps
extern crate bytesize;
extern crate hostname;
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))]
extern crate users;
// locals
use super::{browser::FileExplorerTab, Context, FileTransferActivity};
@@ -49,6 +49,7 @@ use tuirealm::components::{
input::{Input, InputPropsBuilder},
progress_bar::{ProgressBar, ProgressBarPropsBuilder},
radio::{Radio, RadioPropsBuilder},
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
span::{Span, SpanPropsBuilder},
table::{Table, TablePropsBuilder},
};
@@ -58,7 +59,7 @@ use tuirealm::tui::{
style::Color,
widgets::{BorderType, Borders, Clear},
};
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))]
use users::{get_group_by_gid, get_user_by_uid};
impl FileTransferActivity {
@@ -99,13 +100,18 @@ impl FileTransferActivity {
.build(),
)),
);
// Mount status bar
// Mount status bars
self.view.mount(
super::COMPONENT_SPAN_STATUS_BAR,
super::COMPONENT_SPAN_STATUS_BAR_LOCAL,
Box::new(Span::new(SpanPropsBuilder::default().build())),
);
self.view.mount(
super::COMPONENT_SPAN_STATUS_BAR_REMOTE,
Box::new(Span::new(SpanPropsBuilder::default().build())),
);
// Load process bar
self.refresh_status_bar();
self.refresh_local_status_bar();
self.refresh_remote_status_bar();
// Update components
let _ = self.update_local_filelist();
let _ = self.update_remote_filelist();
@@ -144,6 +150,12 @@ impl FileTransferActivity {
.constraints([Constraint::Length(1), Constraint::Length(10)].as_ref())
.direction(Direction::Vertical)
.split(chunks[1]);
// Create status bar chunks
let status_bar_chunks = Layout::default()
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.direction(Direction::Horizontal)
.horizontal_margin(1)
.split(bottom_chunks[0]);
// If width is unset in the storage, set width
if !store.isset(super::STORAGE_EXPLORER_WIDTH) {
store.set_unsigned(super::STORAGE_EXPLORER_WIDTH, tabs_chunks[0].width as usize);
@@ -169,11 +181,20 @@ impl FileTransferActivity {
.view
.render(super::COMPONENT_EXPLORER_REMOTE, f, tabs_chunks[1]),
}
// Draw log box and status bar
// Draw log box
self.view
.render(super::COMPONENT_LOG_BOX, f, bottom_chunks[1]);
self.view
.render(super::COMPONENT_SPAN_STATUS_BAR, f, bottom_chunks[0]);
// Draw status bar
self.view.render(
super::COMPONENT_SPAN_STATUS_BAR_LOCAL,
f,
status_bar_chunks[0],
);
self.view.render(
super::COMPONENT_SPAN_STATUS_BAR_REMOTE,
f,
status_bar_chunks[1],
);
// @! Draw popups
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_COPY) {
if props.visible {
@@ -817,7 +838,7 @@ impl FileTransferActivity {
.build(),
);
// User
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))]
let username: String = match file.get_user() {
Some(uid) => match get_user_by_uid(uid) {
Some(user) => user.name().to_string_lossy().to_string(),
@@ -828,7 +849,7 @@ impl FileTransferActivity {
#[cfg(target_os = "windows")]
let username: String = format!("{}", file.get_user().unwrap_or(0));
// Group
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
#[cfg(any(target_family = "unix", target_os = "macos", target_os = "linux"))]
let group: String = match file.get_group() {
Some(gid) => match get_group_by_gid(gid) {
Some(group) => group.name().to_string_lossy().to_string(),
@@ -864,9 +885,54 @@ impl FileTransferActivity {
self.view.umount(super::COMPONENT_LIST_FILEINFO);
}
pub(super) fn refresh_status_bar(&mut self) {
let bar_spans: Vec<TextSpan> = vec![
TextSpanBuilder::new("Synchronized Browsing: ")
pub(super) fn refresh_local_status_bar(&mut self) {
let local_bar_spans: Vec<TextSpan> = vec![
TextSpanBuilder::new("File sorting: ")
.with_foreground(Color::LightYellow)
.build(),
TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting()))
.with_foreground(Color::LightYellow)
.reversed()
.build(),
TextSpanBuilder::new(" Hidden files: ")
.with_foreground(Color::LightBlue)
.build(),
TextSpanBuilder::new(Self::get_hidden_files_str(
self.local().hidden_files_visible(),
))
.with_foreground(Color::LightBlue)
.reversed()
.build(),
];
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_LOCAL) {
self.view.update(
super::COMPONENT_SPAN_STATUS_BAR_LOCAL,
SpanPropsBuilder::from(props)
.with_spans(local_bar_spans)
.build(),
);
}
}
pub(super) fn refresh_remote_status_bar(&mut self) {
let remote_bar_spans: Vec<TextSpan> = vec![
TextSpanBuilder::new("File sorting: ")
.with_foreground(Color::LightYellow)
.build(),
TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting()))
.with_foreground(Color::LightYellow)
.reversed()
.build(),
TextSpanBuilder::new(" Hidden files: ")
.with_foreground(Color::LightBlue)
.build(),
TextSpanBuilder::new(Self::get_hidden_files_str(
self.remote().hidden_files_visible(),
))
.with_foreground(Color::LightBlue)
.reversed()
.build(),
TextSpanBuilder::new(" Sync Browsing: ")
.with_foreground(Color::LightGreen)
.build(),
TextSpanBuilder::new(match self.browser.sync_browsing {
@@ -876,25 +942,13 @@ impl FileTransferActivity {
.with_foreground(Color::LightGreen)
.reversed()
.build(),
TextSpanBuilder::new(" Localhost file sorting: ")
.with_foreground(Color::LightYellow)
.build(),
TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting()))
.with_foreground(Color::LightYellow)
.reversed()
.build(),
TextSpanBuilder::new(" Remote host file sorting: ")
.with_foreground(Color::LightBlue)
.build(),
TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting()))
.with_foreground(Color::LightBlue)
.reversed()
.build(),
];
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR) {
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_REMOTE) {
self.view.update(
super::COMPONENT_SPAN_STATUS_BAR,
SpanPropsBuilder::from(props).with_spans(bar_spans).build(),
super::COMPONENT_SPAN_STATUS_BAR_REMOTE,
SpanPropsBuilder::from(props)
.with_spans(remote_bar_spans)
.build(),
);
}
}
@@ -905,9 +959,12 @@ impl FileTransferActivity {
pub(super) fn mount_help(&mut self) {
self.view.mount(
super::COMPONENT_TEXT_HELP,
Box::new(Table::new(
TablePropsBuilder::default()
Box::new(Scrolltable::new(
ScrollTablePropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
.with_highlighted_str(Some("?"))
.with_max_scroll_step(8)
.bold()
.with_table(
Some(String::from("Help")),
TableBuilder::default()
@@ -1171,4 +1228,11 @@ impl FileTransferActivity {
FileSorting::BySize => "By size",
}
}
fn get_hidden_files_str(show: bool) -> &'static str {
match show {
true => "Show",
false => "Hide",
}
}
}