mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Merge branch '0.5.1' into 0.6.0
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 => {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user