Open file on <SUBMIT>

This commit is contained in:
veeso 2021-06-10 11:08:17 +02:00
parent c4907c8ce5
commit a8354ee38f
7 changed files with 253 additions and 20 deletions

View file

@ -37,8 +37,10 @@ pub(crate) mod exec;
pub(crate) mod find;
pub(crate) mod mkdir;
pub(crate) mod newfile;
pub(crate) mod open;
pub(crate) mod rename;
pub(crate) mod save;
pub(crate) mod submit;
pub(crate) enum SelectedEntry {
One(FsEntry),

View file

@ -0,0 +1,102 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// deps
extern crate open;
// locals
use super::{FileTransferActivity, FsEntry, LogLevel};
impl FileTransferActivity {
/// ### action_open_local
///
/// Open local file
pub(crate) fn action_open_local(&mut self, entry: FsEntry, open_with: Option<String>) {
let real_entry: FsEntry = entry.get_realfile();
if let FsEntry::File(file) = real_entry {
// Open file
let result = match open_with {
None => open::that(file.abs_path.as_path()),
Some(with) => open::with(file.abs_path.as_path(), with),
};
// Log result
match result {
Ok(_) => self.log(
LogLevel::Info,
format!("Opened file `{}`", entry.get_abs_path().display(),),
),
Err(err) => self.log(
LogLevel::Error,
format!(
"Failed to open filoe `{}`: {}",
entry.get_abs_path().display(),
err
),
),
}
}
}
/// ### action_open_local
///
/// Open remote file. The file is first downloaded to a temporary directory on localhost
pub(crate) fn action_open_remote(&mut self, entry: FsEntry, open_with: Option<String>) {
let real_entry: FsEntry = entry.get_realfile();
if let FsEntry::File(file) = real_entry {
// Download file
let tmp = match self.download_file_as_temp(&file) {
Ok(f) => f,
Err(err) => {
self.log(
LogLevel::Error,
format!("Could not open `{}`: {}", file.abs_path.display(), err),
);
return;
}
};
// Open file
let result = match open_with {
None => open::that(tmp.as_path()),
Some(with) => open::with(tmp.as_path(), with),
};
// Log result
match result {
Ok(_) => self.log(
LogLevel::Info,
format!("Opened file `{}`", entry.get_abs_path().display()),
),
Err(err) => self.log(
LogLevel::Error,
format!(
"Failed to open filoe `{}`: {}",
entry.get_abs_path().display(),
err
),
),
}
}
}
}

View file

@ -0,0 +1,94 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// locals
use super::{FileTransferActivity, FsEntry};
enum SubmitAction {
ChangeDir,
OpenFile,
}
impl FileTransferActivity {
/// ### action_submit_local
///
/// Decides which action to perform on submit for local explorer
/// Return true whether the directory changed
pub(crate) fn action_submit_local(&mut self, entry: FsEntry) -> bool {
let action: SubmitAction = match &entry {
FsEntry::Directory(_) => SubmitAction::ChangeDir,
FsEntry::File(file) => {
match &file.symlink {
Some(symlink_entry) => {
// If symlink and is directory, point to symlink
match &**symlink_entry {
FsEntry::Directory(_) => SubmitAction::ChangeDir,
_ => SubmitAction::OpenFile,
}
}
None => SubmitAction::OpenFile,
}
}
};
match action {
SubmitAction::ChangeDir => self.action_enter_local_dir(entry, false),
SubmitAction::OpenFile => {
self.action_open_local(entry, None);
false
}
}
}
/// ### action_submit_remote
///
/// Decides which action to perform on submit for remote explorer
/// Return true whether the directory changed
pub(crate) fn action_submit_remote(&mut self, entry: FsEntry) -> bool {
let action: SubmitAction = match &entry {
FsEntry::Directory(_) => SubmitAction::ChangeDir,
FsEntry::File(file) => {
match &file.symlink {
Some(symlink_entry) => {
// If symlink and is directory, point to symlink
match &**symlink_entry {
FsEntry::Directory(_) => SubmitAction::ChangeDir,
_ => SubmitAction::OpenFile,
}
}
None => SubmitAction::OpenFile,
}
}
};
match action {
SubmitAction::ChangeDir => self.action_enter_remote_dir(entry, false),
SubmitAction::OpenFile => {
self.action_open_remote(entry, None);
false
}
}
}
}

View file

@ -58,6 +58,7 @@ use chrono::{DateTime, Local};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::collections::VecDeque;
use std::path::PathBuf;
use tempfile::TempDir;
use tuirealm::View;
// -- Storage keys
@ -134,6 +135,7 @@ pub struct FileTransferActivity {
browser: Browser, // Browser
log_records: VecDeque<LogRecord>, // Log records
transfer: TransferStates, // Transfer states
cache: Option<TempDir>, // Temporary directory where to store stuff
}
impl FileTransferActivity {
@ -160,6 +162,10 @@ impl FileTransferActivity {
browser: Browser::new(config_client.as_ref()),
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
transfer: TransferStates::default(),
cache: match TempDir::new() {
Ok(d) => Some(d),
Err(_) => None,
},
}
}
@ -279,6 +285,12 @@ impl Activity for FileTransferActivity {
/// `on_destroy` is the function which cleans up runtime variables and data before terminating the activity.
/// This function must be called once before terminating the activity.
fn on_destroy(&mut self) -> Option<Context> {
// Destroy cache
if let Some(cache) = self.cache.take() {
if let Err(err) = cache.close() {
error!("Failed to delete cache: {}", err);
}
}
// Disable raw mode
if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);

View file

@ -844,38 +844,32 @@ impl FileTransferActivity {
/// 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));
}
let tmpfile: PathBuf = match self.download_file_as_temp(file) {
Ok(p) => p,
Err(err) => return Err(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()) {
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.path().display(),
tmpfile.as_path().display(),
err
))
}
};
// Edit file
if let Err(err) = self.edit_local_file(tmpfile.path()) {
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.path()) {
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
tmpfile.as_path().display(),
err
))
}
@ -891,12 +885,12 @@ impl FileTransferActivity {
),
);
// Get local fs entry
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.path()) {
let tmpfile_entry: FsEntry = match self.host.stat(tmpfile.as_path()) {
Ok(e) => e,
Err(err) => {
return Err(format!(
"Could not stat \"{}\": {}",
tmpfile.path().display(),
tmpfile.as_path().display(),
err
))
}
@ -1035,6 +1029,33 @@ impl FileTransferActivity {
}
}
/// ### download_file_as_temp
///
/// Download provided file as a temporary file
pub(super) fn download_file_as_temp(&mut self, file: &FsFile) -> Result<PathBuf, String> {
let tmpfile: PathBuf = match self.cache.as_ref() {
Some(cache) => {
let mut p: PathBuf = cache.path().to_path_buf();
p.push(file.name.as_str());
p
}
None => {
return Err(String::from(
"Could not create tempfile: cache not available",
))
}
};
// Download file
match self.filetransfer_recv_file(tmpfile.as_path(), file, file.name.clone()) {
Err(err) => Err(format!(
"Could not download {} to temporary file: {}",
file.abs_path.display(),
err
)),
Ok(()) => Ok(tmpfile),
}
}
// -- transfer sizes
/// ### get_total_transfer_size_local

View file

@ -87,7 +87,7 @@ impl Update for FileTransferActivity {
entry = Some(e.clone());
}
if let Some(entry) = entry {
if self.action_enter_local_dir(entry, false) {
if self.action_submit_local(entry) {
// Update file list if sync
if self.browser.sync_browsing {
let _ = self.update_remote_filelist();
@ -150,7 +150,7 @@ impl Update for FileTransferActivity {
entry = Some(e.clone());
}
if let Some(entry) = entry {
if self.action_enter_remote_dir(entry, false) {
if self.action_submit_remote(entry) {
// Update file list if sync
if self.browser.sync_browsing {
let _ = self.update_local_filelist();

View file

@ -934,7 +934,7 @@ impl FileTransferActivity {
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Enter directory"))
.add_col(TextSpan::from(" Enter directory / Open file"))
.add_row()
.add_col(
TextSpanBuilder::new("<SPACE>")
@ -1030,7 +1030,9 @@ impl FileTransferActivity {
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Open text file"))
.add_col(TextSpan::from(
" Open text file with preferred editor",
))
.add_row()
.add_col(
TextSpanBuilder::new("<Q>")