FileTransferActivity::Explorer refactoring; toggle hidden files with <A>

This commit is contained in:
ChristianVisintin 2020-12-26 15:50:57 +01:00
parent 5b042e86ef
commit e0d9ac2ed8
9 changed files with 679 additions and 185 deletions

View file

@ -35,7 +35,10 @@ FIXME: Released on
- Enhancements:
- Replaced `sha256` sum with last modification time check, to verify if a file has been changed in the text editor
- Default protocol changed to default protocol in configuration when providing address as CLI argument
- Explorers:
- Hidden files are now not shown by default; use `A` to show hidden files.
- Keybindings:
- `A`: Toggle hidden files
- `N`: New file
- Dependencies:
- removed `data-encoding`

View file

@ -290,9 +290,10 @@ You can access the SSH key storage, from configuration moving to the `SSH Keys`
| `<PGDOWN>` | Move down in selected list by 8 rows | |
| `<ENTER>` | Enter directory | |
| `<SPACE>` | Upload / download selected file | |
| `<A>` | Toggle hidden files | All |
| `<C>` | Copy file/directory | Copy |
| `<D>` | Make directory | Directory |
| `<E>` | Delete file (Same as `CANC`) | Erase |
| `<E>` | Delete file (Same as `DEL`) | Erase |
| `<G>` | Go to supplied path | Go to |
| `<H>` | Show help | Help |
| `<I>` | Show info about selected file or directory | Info |

View file

@ -212,7 +212,7 @@ impl FsEntry {
///
/// Returns whether FsEntry is hidden
pub fn is_hidden(&self) -> bool {
self.get_name().starts_with(".")
self.get_name().starts_with('.')
}
/// ### get_realfile

View file

@ -1,3 +1,7 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/*
*
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
@ -71,8 +75,8 @@ impl FileTransferActivity {
match self.tab {
FileExplorerTab::Local => {
// Get selected entry
if self.local.files.get(self.local.index).is_some() {
let entry: FsEntry = self.local.files.get(self.local.index).unwrap().clone();
if self.local.get_current_file().is_some() {
let entry: FsEntry = self.local.get_current_file().unwrap().clone();
if let Some(ctx) = self.context.as_mut() {
match ctx.local.copy(&entry, dest_path.as_path()) {
Ok(_) => {
@ -104,8 +108,8 @@ impl FileTransferActivity {
}
FileExplorerTab::Remote => {
// Get selected entry
if self.remote.files.get(self.remote.index).is_some() {
let entry: FsEntry = self.remote.files.get(self.remote.index).unwrap().clone();
if self.remote.get_current_file().is_some() {
let entry: FsEntry = self.remote.get_current_file().unwrap().clone();
match self.client.as_mut().copy(&entry, dest_path.as_path()) {
Ok(_) => {
self.log(
@ -205,7 +209,7 @@ impl FileTransferActivity {
dst_path = wrkdir;
}
// Check if file entry exists
if let Some(entry) = self.local.files.get(self.local.index) {
if let Some(entry) = self.local.get_current_file() {
let full_path: PathBuf = entry.get_abs_path();
// Rename file or directory and report status as popup
match self
@ -245,7 +249,7 @@ impl FileTransferActivity {
}
FileExplorerTab::Remote => {
// Check if file entry exists
if let Some(entry) = self.remote.files.get(self.remote.index) {
if let Some(entry) = self.remote.get_current_file() {
let full_path: PathBuf = entry.get_abs_path();
// Rename file or directory and report status as popup
let dst_path: PathBuf = PathBuf::from(input);
@ -289,7 +293,7 @@ impl FileTransferActivity {
match self.tab {
FileExplorerTab::Local => {
// Check if file entry exists
if let Some(entry) = self.local.files.get(self.local.index) {
if let Some(entry) = self.local.get_current_file() {
let full_path: PathBuf = entry.get_abs_path();
// Delete file or directory and report status as popup
match self.context.as_mut().unwrap().local.remove(entry) {
@ -318,7 +322,7 @@ impl FileTransferActivity {
}
FileExplorerTab::Remote => {
// Check if file entry exists
if let Some(entry) = self.remote.files.get(self.remote.index) {
if let Some(entry) = self.remote.get_current_file() {
let full_path: PathBuf = entry.get_abs_path();
// Delete file
match self.client.remove(entry) {
@ -355,16 +359,16 @@ impl FileTransferActivity {
// Get pwd
let wrkdir: PathBuf = self.remote.wrkdir.clone();
// Get file and clone (due to mutable / immutable stuff...)
if self.local.files.get(self.local.index).is_some() {
let file: FsEntry = self.local.files.get(self.local.index).unwrap().clone();
if self.local.get_current_file().is_some() {
let file: FsEntry = self.local.get_current_file().unwrap().clone();
// Call upload; pass realfile, keep link name
self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(input));
}
}
FileExplorerTab::Remote => {
// Get file and clone (due to mutable / immutable stuff...)
if self.remote.files.get(self.remote.index).is_some() {
let file: FsEntry = self.remote.files.get(self.remote.index).unwrap().clone();
if self.remote.get_current_file().is_some() {
let file: FsEntry = self.remote.get_current_file().unwrap().clone();
// Call upload; pass realfile, keep link name
let wrkdir: PathBuf = self.local.wrkdir.clone();
self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(input));
@ -380,15 +384,19 @@ impl FileTransferActivity {
match self.tab {
FileExplorerTab::Local => {
// Check if file exists
for file in self.local.files.iter() {
let mut file_exists: bool = false;
for file in self.local.iter_files_all() {
if input == file.get_name() {
self.log_and_alert(
LogLevel::Warn,
format!("File \"{}\" already exists", input,),
);
return;
file_exists = true;
}
}
if file_exists {
self.log_and_alert(
LogLevel::Warn,
format!("File \"{}\" already exists", input,),
);
return;
}
// Create file
let file_path: PathBuf = PathBuf::from(input.as_str());
if let Some(ctx) = self.context.as_mut() {
@ -409,15 +417,19 @@ impl FileTransferActivity {
}
FileExplorerTab::Remote => {
// Check if file exists
for file in self.remote.files.iter() {
let mut file_exists: bool = false;
for file in self.remote.iter_files_all() {
if input == file.get_name() {
self.log_and_alert(
LogLevel::Warn,
format!("File \"{}\" already exists", input,),
);
return;
file_exists = true;
}
}
if file_exists {
self.log_and_alert(
LogLevel::Warn,
format!("File \"{}\" already exists", input,),
);
return;
}
// Get path on remote
let file_path: PathBuf = PathBuf::from(input.as_str());
// Create file (on local)

View file

@ -0,0 +1,532 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/*
*
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
*
* This file is part of "TermSCP"
*
* TermSCP is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TermSCP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Locals
use super::FsEntry;
// Ext
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
/// ## FileExplorer
///
/// File explorer states
pub struct FileExplorer {
pub wrkdir: PathBuf, // Current directory
index: usize, // Selected file
files: Vec<FsEntry>, // Files in directory
dirstack: VecDeque<PathBuf>, // Stack of visited directory (max 16)
stack_size: usize, // Directory stack size
hidden_files: bool, // Should hidden files be shown or not; hidden if false
}
impl FileExplorer {
/// ### new
///
/// Instantiates a new FileExplorer
pub fn new(stack_size: usize) -> FileExplorer {
FileExplorer {
wrkdir: PathBuf::from("/"),
index: 0,
files: Vec::new(),
dirstack: VecDeque::with_capacity(stack_size),
stack_size,
hidden_files: false, // Default: don't show hidden files
}
}
/// ### pushd
///
/// push directory to stack
pub fn pushd(&mut self, dir: &Path) {
// Check if stack would overflow the size
while self.dirstack.len() >= self.stack_size {
self.dirstack.pop_front(); // Start cleaning events from back
}
// Eventually push front the new record
self.dirstack.push_back(PathBuf::from(dir));
}
/// ### popd
///
/// Pop directory from the stack and return the directory
pub fn popd(&mut self) -> Option<PathBuf> {
self.dirstack.pop_back()
}
/// ### set_files
///
/// Set Explorer files
/// Index is then moved to first valid `FsEntry` for current setup
pub fn set_files(&mut self, files: Vec<FsEntry>) {
self.files = files;
// Set index to first valid entry
self.index_at_first();
}
/// ### count
///
/// Return amount of files
pub fn count(&self) -> usize {
self.files.len()
}
/// ### iter_files
///
/// Iterate over files
/// Filters are applied based on current options (e.g. hidden files not returned)
pub fn iter_files(&self) -> Box<dyn Iterator<Item = &FsEntry> + '_> {
// Match options
match self.hidden_files {
false => Box::new(self.files.iter().filter(|x| !x.is_hidden())), // Show only visible files
true => self.iter_files_all(), // Show all
}
}
/// ### iter_files_all
///
/// Iterate all files; doesn't care about options
pub fn iter_files_all(&self) -> Box<dyn Iterator<Item = &FsEntry> + '_> {
Box::new(self.files.iter())
}
/// ### get_current_file
///
/// Get file at index
pub fn get_current_file(&self) -> Option<&FsEntry> {
self.files.get(self.index)
}
/// ### sort_files_by_name
///
/// Sort explorer files by their name. All names are converted to lowercase
pub fn sort_files_by_name(&mut self) {
self.files.sort_by_key(|x: &FsEntry| match x {
FsEntry::Directory(dir) => dir.name.as_str().to_lowercase(),
FsEntry::File(file) => file.name.as_str().to_lowercase(),
});
// Reset index
self.index_at_first();
}
/// ### incr_index
///
/// Increment index to the first visible FsEntry.
/// If index goes to `files.len() - 1`, the value will be seto to the minimum acceptable value
pub fn incr_index(&mut self) {
let sz: usize = self.files.len();
// Increment or wrap
if self.index + 1 >= sz {
self.index = 0; // Wrap
} else {
self.index += 1; // Increment
}
// Validate
match self.files.get(self.index) {
Some(assoc_entry) => {
if !self.hidden_files {
// Check if file is hidden, otherwise increment
if assoc_entry.is_hidden() {
// Check if all files are hidden (NOTE: PREVENT STACK OVERFLOWS)
let hidden_files: usize =
self.files.iter().filter(|x| x.is_hidden()).count();
// Only if there are more files, than hidden files keep incrementing
if sz > hidden_files {
self.incr_index();
}
}
}
}
None => self.index = 0, // Reset to 0, for safety reasons
}
}
/// ### incr_index_by
///
/// Increment index by up to n
/// If index goes to `files.len() - 1`, the value will be seto to the minimum acceptable value
pub fn incr_index_by(&mut self, n: usize) {
for _ in 0..n {
let prev_idx: usize = self.index;
// Increment
self.incr_index();
// If prev index is > index and break
if prev_idx > self.index {
self.index = prev_idx;
break;
}
}
}
/// ### decr_index
///
/// Decrement index to the first visible FsEntry.
/// If index is 0, its value will be set to the maximum acceptable value
pub fn decr_index(&mut self) {
let sz: usize = self.files.len();
// Increment or wrap
if self.index > 0 {
self.index -= 1; // Decrement
} else {
self.index = sz - 1; // Wrap
}
// Validate index
match self.files.get(self.index) {
Some(assoc_entry) => {
if !self.hidden_files {
// Check if file is hidden, otherwise increment
if assoc_entry.is_hidden() {
// Check if all files are hidden (NOTE: PREVENT STACK OVERFLOWS)
let hidden_files: usize =
self.files.iter().filter(|x| x.is_hidden()).count();
// Only if there are more files, than hidden files keep decrementing
if sz > hidden_files {
self.decr_index();
}
}
}
}
None => self.index = 0, // Reset to 0, for safety reasons
}
}
/// ### decr_index_by
///
/// Decrement index by up to n
pub fn decr_index_by(&mut self, n: usize) {
for _ in 0..n {
let prev_idx: usize = self.index;
// Increment
self.decr_index();
// If prev index is < index and break
if prev_idx < self.index {
self.index = prev_idx;
break;
}
}
}
/// ### index_at_first
///
/// Move index to first "visible" fs entry
pub fn index_at_first(&mut self) {
self.index = self.get_first_valid_index();
}
/// ### get_first_valid_index
///
/// Return first valid index
fn get_first_valid_index(&self) -> usize {
match self.hidden_files {
true => 0,
false => {
// Look for first "non-hidden" entry
for (i, f) in self.files.iter().enumerate() {
if !f.is_hidden() {
return i;
}
}
// If all files are hidden, return 0
0
}
}
}
/// ### get_index
///
/// Return index
pub fn get_index(&self) -> usize {
self.index
}
/// ### get_relative_index
///
/// Get relative index based on current options
pub fn get_relative_index(&self) -> usize {
match self.files.get(self.index) {
Some(abs_entry) => {
// Search abs entry in relative iterator
for (i, f) in self.iter_files().enumerate() {
if abs_entry.get_name() == f.get_name() {
// If abs entry is f, return index
return i;
}
}
// Return 0 if not found
0
}
None => 0, // Absolute entry doesn't exist
}
}
/// ### set_index
///
/// Set index to idx.
/// If index exceeds size, is set to count() - 1; or 0
pub fn set_index(&mut self, idx: usize) {
let visible_sz: usize = self.iter_files().count();
match idx >= visible_sz {
true => match visible_sz {
0 => self.index_at_first(),
_ => self.index = visible_sz - 1,
},
false => match self.get_first_valid_index() > idx {
true => self.index_at_first(),
false => self.index = idx,
},
}
}
/// ### toggle_hidden_files
///
/// Enable/disable hidden files
pub fn toggle_hidden_files(&mut self) {
self.hidden_files = !self.hidden_files;
// Adjust index
if self.index < self.get_first_valid_index() {
self.index_at_first();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::{FsDirectory, FsFile};
use std::time::SystemTime;
#[test]
fn test_ui_filetransfer_activity_explorer_new() {
let explorer: FileExplorer = FileExplorer::new(16);
// Verify
assert_eq!(explorer.dirstack.len(), 0);
assert_eq!(explorer.files.len(), 0);
assert_eq!(explorer.hidden_files, false);
assert_eq!(explorer.wrkdir, PathBuf::from("/"));
assert_eq!(explorer.stack_size, 16);
assert_eq!(explorer.index, 0);
}
#[test]
fn test_ui_filetransfer_activity_explorer_stack() {
let mut explorer: FileExplorer = FileExplorer::new(2);
// Push dir
explorer.pushd(&Path::new("/tmp"));
explorer.pushd(&Path::new("/home/omar"));
// Pop
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar"));
assert_eq!(explorer.dirstack.len(), 1);
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/tmp"));
assert_eq!(explorer.dirstack.len(), 0);
// Dirstack is empty now
assert!(explorer.popd().is_none());
// Exceed limit
explorer.pushd(&Path::new("/tmp"));
explorer.pushd(&Path::new("/home/omar"));
explorer.pushd(&Path::new("/dev"));
assert_eq!(explorer.dirstack.len(), 2);
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
assert_eq!(
*explorer.dirstack.get(0).unwrap(),
PathBuf::from("/home/omar")
);
}
#[test]
fn test_ui_filetransfer_activity_explorer_files() {
let mut explorer: FileExplorer = FileExplorer::new(16);
explorer.hidden_files = false;
// Create files
explorer.set_files(vec![
make_fs_entry("README.md", false),
make_fs_entry("src/", true),
make_fs_entry(".git/", true),
make_fs_entry("CONTRIBUTING.md", false),
make_fs_entry("codecov.yml", false),
make_fs_entry(".gitignore", false),
]);
assert_eq!(explorer.count(), 6);
// Verify
assert_eq!(
explorer.files.get(0).unwrap().get_name(),
String::from("README.md")
);
// Sort files
explorer.sort_files_by_name();
// Verify
assert_eq!(
explorer.files.get(0).unwrap().get_name(),
String::from(".git/")
);
// Iter files (all)
assert_eq!(explorer.iter_files_all().count(), 6);
// Iter files (hidden excluded) (.git, .gitignore are hidden)
assert_eq!(explorer.iter_files().count(), 4);
// Toggle hidden
explorer.toggle_hidden_files();
assert_eq!(explorer.iter_files().count(), 6); // All files are returned now
}
#[test]
fn test_ui_filetransfer_activity_explorer_index() {
let mut explorer: FileExplorer = FileExplorer::new(16);
explorer.hidden_files = false;
// Create files
explorer.set_files(vec![
make_fs_entry("README.md", false),
make_fs_entry("src/", true),
make_fs_entry(".git/", true),
make_fs_entry("CONTRIBUTING.md", false),
make_fs_entry("CODE_OF_CONDUCT.md", false),
make_fs_entry("CHANGELOG.md", false),
make_fs_entry("LICENSE", false),
make_fs_entry("Cargo.toml", false),
make_fs_entry("Cargo.lock", false),
make_fs_entry("codecov.yml", false),
make_fs_entry(".gitignore", false),
]);
let sz: usize = explorer.count();
// Sort by name
explorer.sort_files_by_name();
// Get first index
assert_eq!(explorer.get_first_valid_index(), 2);
// Index should be 2 now; files hidden; this happens because `index_at_first` is called after loading files
assert_eq!(explorer.get_index(), 2);
assert_eq!(explorer.get_relative_index(), 0); // Relative index should be 0
assert_eq!(explorer.hidden_files, false);
// Increment index
explorer.incr_index();
// Index should now be 3 (was 0, + 2 + 1); first 2 files are hidden (.git, .gitignore)
assert_eq!(explorer.get_index(), 3);
// Relative index should be 1 instead
assert_eq!(explorer.get_relative_index(), 1);
// Increment by 2
explorer.incr_index_by(2);
// Index should now be 5, 3
assert_eq!(explorer.get_index(), 5);
assert_eq!(explorer.get_relative_index(), 3);
// Increment by (exceed size)
explorer.incr_index_by(20);
// Index should be at last element
assert_eq!(explorer.get_index(), sz - 1);
assert_eq!(explorer.get_relative_index(), sz - 3);
// Increment; should go to 2
explorer.incr_index();
assert_eq!(explorer.get_index(), 2);
assert_eq!(explorer.get_relative_index(), 0);
// Increment and then decrement
explorer.incr_index();
explorer.decr_index();
assert_eq!(explorer.get_index(), 2);
assert_eq!(explorer.get_relative_index(), 0);
// Decrement (and wrap)
explorer.decr_index();
// Index should be at last element
assert_eq!(explorer.get_index(), sz - 1);
assert_eq!(explorer.get_relative_index(), sz - 3);
// Set index to 5
explorer.set_index(5);
assert_eq!(explorer.get_index(), 5);
assert_eq!(explorer.get_relative_index(), 3);
// Decr by 2
explorer.decr_index_by(2);
assert_eq!(explorer.get_index(), 3);
assert_eq!(explorer.get_relative_index(), 1);
// Decr by 2
explorer.decr_index_by(2);
// Should decrement actually by 1 (since first two files are hidden)
assert_eq!(explorer.get_index(), 2);
assert_eq!(explorer.get_relative_index(), 0);
// Toggle hidden files
explorer.toggle_hidden_files();
assert_eq!(explorer.hidden_files, true);
// Move index to 0
explorer.set_index(0);
assert_eq!(explorer.get_index(), 0);
// Toggle hidden files
explorer.toggle_hidden_files();
// Index should now have been moved to 2
assert_eq!(explorer.get_index(), 2);
// Show hidden files
explorer.toggle_hidden_files();
// Set index to 5
explorer.set_index(5);
// Verify index
assert_eq!(explorer.get_index(), 5);
assert_eq!(explorer.get_relative_index(), 5); // Now relative matches
// Decrement by 6, goes to 0
explorer.decr_index_by(6);
assert_eq!(explorer.get_index(), 0);
assert_eq!(explorer.get_relative_index(), 0); // Now relative matches
// Toggle; move at first
explorer.toggle_hidden_files();
assert_eq!(explorer.hidden_files, false);
explorer.index_at_first();
assert_eq!(explorer.get_index(), 2);
assert_eq!(explorer.get_relative_index(), 0);
// Verify set index if exceeds
let sz: usize = explorer.iter_files().count();
explorer.set_index(sz);
assert_eq!(explorer.get_index(), sz - 1); // Should be at last element
// Empty files
explorer.files.clear();
explorer.index_at_first();
assert_eq!(explorer.get_index(), 0);
assert_eq!(explorer.get_relative_index(), 0);
}
fn make_fs_entry(name: &str, is_dir: bool) -> FsEntry {
let t_now: SystemTime = SystemTime::now();
match is_dir {
false => FsEntry::File(FsFile {
name: name.to_string(),
abs_path: PathBuf::from(name),
last_change_time: t_now,
last_access_time: t_now,
creation_time: t_now,
size: 64,
ftype: None, // File type
readonly: false,
symlink: None, // UNIX only
user: Some(0), // UNIX only
group: Some(0), // UNIX only
unix_pex: Some((6, 4, 4)), // UNIX only
}),
true => FsEntry::Directory(FsDirectory {
name: name.to_string(),
abs_path: PathBuf::from(name),
last_change_time: t_now,
last_access_time: t_now,
creation_time: t_now,
readonly: false,
symlink: None, // UNIX only
user: Some(0), // UNIX only
group: Some(0), // UNIX only
unix_pex: Some((7, 5, 5)), // UNIX only
}),
}
}
}

View file

@ -1,3 +1,7 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/*
*
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
@ -102,41 +106,28 @@ impl FileTransferActivity {
KeyCode::Tab => self.switch_input_field(), // <TAB> switch tab
KeyCode::Right => self.tab = FileExplorerTab::Remote, // <RIGHT> switch to right tab
KeyCode::Up => {
// Move index up; or move to the last element if 0
self.local.index = match self.local.index {
0 => self.local.files.len() - 1,
_ => self.local.index - 1,
};
// Decrement index
self.local.decr_index();
}
KeyCode::Down => {
// Move index down
if self.local.index + 1 < self.local.files.len() {
self.local.index += 1;
} else {
self.local.index = 0; // Move at the beginning of the list
}
// Increment index
self.local.incr_index();
}
KeyCode::PageUp => {
// Move index up (fast)
if self.local.index > 8 {
self.local.index -= 8; // Decrease by `8` if possible
} else {
self.local.index = 0; // Set to 0 otherwise
}
// Decrement index by 8
self.local.decr_index_by(8);
}
KeyCode::PageDown => {
// Move index down (fast)
if self.local.index + 8 >= self.local.files.len() {
// If overflows, set to size
self.local.index = self.local.files.len() - 1;
} else {
self.local.index += 8; // Increase by `8`
}
// Increment index by 8
self.local.incr_index_by(8);
}
KeyCode::Enter => {
// Match selected file
let local_files: Vec<FsEntry> = self.local.files.clone();
if let Some(entry) = local_files.get(self.local.index) {
let mut entry: Option<FsEntry> = None;
if let Some(e) = self.local.get_current_file() {
entry = Some(e.clone());
}
if let Some(entry) = entry {
// If directory, enter directory, otherwise check if symlink
match entry {
FsEntry::Directory(dir) => {
@ -162,7 +153,7 @@ impl FileTransferActivity {
}
KeyCode::Delete => {
// Get file at index
if let Some(entry) = self.local.files.get(self.local.index) {
if let Some(entry) = self.local.get_current_file() {
// Get file name
let file_name: String = match entry {
FsEntry::Directory(dir) => dir.name.clone(),
@ -177,6 +168,10 @@ impl FileTransferActivity {
}
}
KeyCode::Char(ch) => match ch {
'a' | 'A' => {
// Toggle hidden files
self.local.toggle_hidden_files();
}
'c' | 'C' => {
// Copy
self.input_mode = InputMode::Popup(PopupType::Input(
@ -193,7 +188,7 @@ impl FileTransferActivity {
}
'e' | 'E' => {
// Get file at index
if let Some(entry) = self.local.files.get(self.local.index) {
if let Some(entry) = self.local.get_current_file() {
// Get file name
let file_name: String = match entry {
FsEntry::Directory(dir) => dir.name.clone(),
@ -237,10 +232,9 @@ impl FileTransferActivity {
}
'o' | 'O' => {
// Edit local file
if self.local.files.get(self.local.index).is_some() {
if self.local.get_current_file().is_some() {
// Clone entry due to mutable stuff...
let fsentry: FsEntry =
self.local.files.get(self.local.index).unwrap().clone();
let fsentry: FsEntry = self.local.get_current_file().unwrap().clone();
// Check if file
if fsentry.is_file() {
self.log(
@ -294,9 +288,8 @@ impl FileTransferActivity {
// Get pwd
let wrkdir: PathBuf = self.remote.wrkdir.clone();
// Get file and clone (due to mutable / immutable stuff...)
if self.local.files.get(self.local.index).is_some() {
let file: FsEntry =
self.local.files.get(self.local.index).unwrap().clone();
if self.local.get_current_file().is_some() {
let file: FsEntry = self.local.get_current_file().unwrap().clone();
let name: String = file.get_name().to_string();
// Call upload; pass realfile, keep link name
self.filetransfer_send(
@ -328,41 +321,28 @@ impl FileTransferActivity {
KeyCode::Tab => self.switch_input_field(), // <TAB> switch tab
KeyCode::Left => self.tab = FileExplorerTab::Local, // <LEFT> switch to local tab
KeyCode::Up => {
// Move index up; or move to the last element if 0
self.remote.index = match self.remote.index {
0 => self.remote.files.len() - 1,
_ => self.remote.index - 1,
};
// Decrement index
self.remote.decr_index();
}
KeyCode::Down => {
// Move index down
if self.remote.index + 1 < self.remote.files.len() {
self.remote.index += 1;
} else {
self.remote.index = 0; // Move at the beginning of the list
}
// Increment index
self.remote.incr_index();
}
KeyCode::PageUp => {
// Move index up (fast)
if self.remote.index > 8 {
self.remote.index -= 8; // Decrease by `8` if possible
} else {
self.remote.index = 0; // Set to 0 otherwise
}
// Decrement index by 8
self.remote.decr_index_by(8);
}
KeyCode::PageDown => {
// Move index down (fast)
if self.remote.index + 8 >= self.remote.files.len() {
// If overflows, set to size
self.remote.index = self.remote.files.len() - 1;
} else {
self.remote.index += 8; // Increase by `8`
}
// Increment index by 8
self.remote.incr_index_by(8);
}
KeyCode::Enter => {
// Match selected file
let files: Vec<FsEntry> = self.remote.files.clone();
if let Some(entry) = files.get(self.remote.index) {
let mut entry: Option<FsEntry> = None;
if let Some(e) = self.remote.get_current_file() {
entry = Some(e.clone());
}
if let Some(entry) = entry {
// If directory, enter directory; if file, check if is symlink
match entry {
FsEntry::Directory(dir) => {
@ -388,7 +368,7 @@ impl FileTransferActivity {
}
KeyCode::Delete => {
// Get file at index
if let Some(entry) = self.remote.files.get(self.remote.index) {
if let Some(entry) = self.remote.get_current_file() {
// Get file name
let file_name: String = match entry {
FsEntry::Directory(dir) => dir.name.clone(),
@ -403,6 +383,10 @@ impl FileTransferActivity {
}
}
KeyCode::Char(ch) => match ch {
'a' | 'A' => {
// Toggle hidden files
self.remote.toggle_hidden_files();
}
'c' | 'C' => {
// Copy
self.input_mode = InputMode::Popup(PopupType::Input(
@ -419,7 +403,7 @@ impl FileTransferActivity {
}
'e' | 'E' => {
// Get file at index
if let Some(entry) = self.remote.files.get(self.remote.index) {
if let Some(entry) = self.remote.get_current_file() {
// Get file name
let file_name: String = match entry {
FsEntry::Directory(dir) => dir.name.clone(),
@ -462,10 +446,9 @@ impl FileTransferActivity {
}
'o' | 'O' => {
// Edit remote file
if self.remote.files.get(self.remote.index).is_some() {
if self.remote.get_current_file().is_some() {
// Clone entry due to mutable stuff...
let fsentry: FsEntry =
self.remote.files.get(self.remote.index).unwrap().clone();
let fsentry: FsEntry = self.remote.get_current_file().unwrap().clone();
// Check if file
if let FsEntry::File(file) = fsentry {
self.log(
@ -516,9 +499,8 @@ impl FileTransferActivity {
}
' ' => {
// Get file and clone (due to mutable / immutable stuff...)
if self.remote.files.get(self.remote.index).is_some() {
let file: FsEntry =
self.remote.files.get(self.remote.index).unwrap().clone();
if self.remote.get_current_file().is_some() {
let file: FsEntry = self.remote.get_current_file().unwrap().clone();
let name: String = file.get_name().to_string();
// Call upload; pass realfile, keep link name
let wrkdir: PathBuf = self.local.wrkdir.clone();

View file

@ -1,3 +1,7 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/*
*
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
@ -70,10 +74,10 @@ impl FileTransferActivity {
.split(chunks[0]);
// Set localhost state
let mut localhost_state: ListState = ListState::default();
localhost_state.select(Some(self.local.index));
localhost_state.select(Some(self.local.get_relative_index()));
// Set remote state
let mut remote_state: ListState = ListState::default();
remote_state.select(Some(self.remote.index));
remote_state.select(Some(self.remote.get_relative_index()));
// Draw tabs
f.render_stateful_widget(
self.draw_local_explorer(tabs_chunks[0].width),
@ -158,8 +162,7 @@ impl FileTransferActivity {
};
let files: Vec<ListItem> = self
.local
.files
.iter()
.iter_files()
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
.collect();
// Get colors to use; highlight element inverting fg/bg only when tab is active
@ -199,8 +202,7 @@ impl FileTransferActivity {
pub(super) fn draw_remote_explorer(&self, width: u16) -> List {
let files: Vec<ListItem> = self
.remote
.files
.iter()
.iter_files()
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
.collect();
// Get colors to use; highlight element inverting fg/bg only when tab is active
@ -468,12 +470,12 @@ impl FileTransferActivity {
let fsentry: Option<&FsEntry> = match self.tab {
FileExplorerTab::Local => {
// Get selected file
match self.local.files.get(self.local.index) {
match self.local.get_current_file() {
Some(entry) => Some(entry),
None => None,
}
}
FileExplorerTab::Remote => match self.remote.files.get(self.remote.index) {
FileExplorerTab::Remote => match self.remote.get_current_file() {
Some(entry) => Some(entry),
None => None,
},
@ -715,6 +717,16 @@ impl FileTransferActivity {
Span::raw(" "),
Span::raw("Delete file"),
])),
ListItem::new(Spans::from(vec![
Span::styled(
"<A>",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Span::raw(" "),
Span::raw("Toggle hidden files"),
])),
ListItem::new(Spans::from(vec![
Span::styled(
"<C>",

View file

@ -25,6 +25,7 @@
// This module is split into files, cause it's just too big
mod callbacks;
mod explorer;
mod input;
mod layout;
mod misc;
@ -45,13 +46,14 @@ use crate::filetransfer::sftp_transfer::SftpFileTransfer;
use crate::filetransfer::{FileTransfer, FileTransferProtocol};
use crate::fs::FsEntry;
use crate::system::config_client::ConfigClient;
use explorer::FileExplorer;
// Includes
use chrono::{DateTime, Local};
use crossterm::event::Event as InputEvent;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::time::Instant;
use tui::style::Color;
@ -113,59 +115,6 @@ enum InputMode {
Popup(PopupType),
}
/// ## FileExplorer
///
/// File explorer states
struct FileExplorer {
pub wrkdir: PathBuf, // Current directory
pub index: usize, // Selected file
pub files: Vec<FsEntry>, // Files in directory
dirstack: VecDeque<PathBuf>, // Stack of visited directory (max 16)
}
impl FileExplorer {
/// ### new
///
/// Instantiates a new FileExplorer
pub fn new() -> FileExplorer {
FileExplorer {
wrkdir: PathBuf::from("/"),
index: 0,
files: Vec::new(),
dirstack: VecDeque::with_capacity(16),
}
}
/// ### pushd
///
/// push directory to stack
pub fn pushd(&mut self, dir: &Path) {
// Check if stack overflows the size
if self.dirstack.len() + 1 > 16 {
self.dirstack.pop_back(); // Start cleaning events from back
}
// Eventually push front the new record
self.dirstack.push_front(PathBuf::from(dir));
}
/// ### popd
///
/// Pop directory from the stack and return the directory
pub fn popd(&mut self) -> Option<PathBuf> {
self.dirstack.pop_front()
}
/// ### sort_files_by_name
///
/// Sort explorer files by their name
pub fn sort_files_by_name(&mut self) {
self.files.sort_by_key(|x: &FsEntry| match x {
FsEntry::Directory(dir) => dir.name.as_str().to_lowercase(),
FsEntry::File(file) => file.name.as_str().to_lowercase(),
});
}
}
/// ## FileExplorerTab
///
/// File explorer tab
@ -312,18 +261,18 @@ impl FileTransferActivity {
quit: false,
context: None,
client: match protocol {
FileTransferProtocol::Sftp => {
Box::new(SftpFileTransfer::new(Self::make_ssh_storage(config_client.as_ref())))
}
FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new(
Self::make_ssh_storage(config_client.as_ref()),
)),
FileTransferProtocol::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)),
FileTransferProtocol::Scp => {
Box::new(ScpFileTransfer::new(Self::make_ssh_storage(config_client.as_ref())))
}
FileTransferProtocol::Scp => Box::new(ScpFileTransfer::new(
Self::make_ssh_storage(config_client.as_ref()),
)),
},
config_cli: config_client,
params,
local: FileExplorer::new(),
remote: FileExplorer::new(),
local: FileExplorer::new(16),
remote: FileExplorer::new(16),
tab: FileExplorerTab::Local,
log_index: 0,
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
@ -335,7 +284,6 @@ impl FileTransferActivity {
transfer: TransferStates::default(),
}
}
}
/**

View file

@ -1,3 +1,7 @@
//! ## FileTransferActivity
//!
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
/*
*
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
@ -599,17 +603,17 @@ impl FileTransferActivity {
pub(super) fn local_scan(&mut self, path: &Path) {
match self.context.as_ref().unwrap().local.scan_dir(path) {
Ok(files) => {
self.local.files = files;
// Set index; keep if possible, otherwise set to last item
self.local.index = match self.local.files.get(self.local.index) {
Some(_) => self.local.index,
None => match self.local.files.len() {
0 => 0,
_ => self.local.files.len() - 1,
},
};
self.local.set_files(files);
// Sort files
self.local.sort_files_by_name();
// Set index; keep if possible, otherwise set to last item
self.local.set_index(match self.local.get_current_file() {
Some(_) => self.local.get_index(),
None => match self.local.count() {
0 => 0,
_ => self.local.count() - 1,
},
});
}
Err(err) => {
self.log_and_alert(
@ -626,17 +630,17 @@ impl FileTransferActivity {
pub(super) fn remote_scan(&mut self, path: &Path) {
match self.client.list_dir(path) {
Ok(files) => {
self.remote.files = files;
// Set index; keep if possible, otherwise set to last item
self.remote.index = match self.remote.files.get(self.remote.index) {
Some(_) => self.remote.index,
None => match self.remote.files.len() {
0 => 0,
_ => self.remote.files.len() - 1,
},
};
self.remote.set_files(files);
// Sort files
self.remote.sort_files_by_name();
// Set index; keep if possible, otherwise set to last item
self.remote.set_index(match self.remote.get_current_file() {
Some(_) => self.remote.get_index(),
None => match self.remote.count() {
0 => 0,
_ => self.remote.count() - 1,
},
});
}
Err(err) => {
self.log_and_alert(
@ -663,7 +667,7 @@ impl FileTransferActivity {
// Reload files
self.local_scan(path);
// Reset index
self.local.index = 0;
self.local.set_index(0);
// Set wrkdir
self.local.wrkdir = PathBuf::from(path);
// Push prev_dir to stack
@ -694,7 +698,7 @@ impl FileTransferActivity {
// Update files
self.remote_scan(path);
// Reset index
self.remote.index = 0;
self.remote.set_index(0);
// Set wrkdir
self.remote.wrkdir = PathBuf::from(path);
// Push prev_dir to stack