mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 01:26:04 -08:00
FileTransferActivity::Explorer refactoring; toggle hidden files with <A>
This commit is contained in:
@@ -35,7 +35,10 @@ FIXME: Released on
|
|||||||
- Enhancements:
|
- Enhancements:
|
||||||
- Replaced `sha256` sum with last modification time check, to verify if a file has been changed in the text editor
|
- 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
|
- 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:
|
- Keybindings:
|
||||||
|
- `A`: Toggle hidden files
|
||||||
- `N`: New file
|
- `N`: New file
|
||||||
- Dependencies:
|
- Dependencies:
|
||||||
- removed `data-encoding`
|
- removed `data-encoding`
|
||||||
|
|||||||
@@ -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 | |
|
| `<PGDOWN>` | Move down in selected list by 8 rows | |
|
||||||
| `<ENTER>` | Enter directory | |
|
| `<ENTER>` | Enter directory | |
|
||||||
| `<SPACE>` | Upload / download selected file | |
|
| `<SPACE>` | Upload / download selected file | |
|
||||||
|
| `<A>` | Toggle hidden files | All |
|
||||||
| `<C>` | Copy file/directory | Copy |
|
| `<C>` | Copy file/directory | Copy |
|
||||||
| `<D>` | Make directory | Directory |
|
| `<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 |
|
| `<G>` | Go to supplied path | Go to |
|
||||||
| `<H>` | Show help | Help |
|
| `<H>` | Show help | Help |
|
||||||
| `<I>` | Show info about selected file or directory | Info |
|
| `<I>` | Show info about selected file or directory | Info |
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ impl FsEntry {
|
|||||||
///
|
///
|
||||||
/// Returns whether FsEntry is hidden
|
/// Returns whether FsEntry is hidden
|
||||||
pub fn is_hidden(&self) -> bool {
|
pub fn is_hidden(&self) -> bool {
|
||||||
self.get_name().starts_with(".")
|
self.get_name().starts_with('.')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### get_realfile
|
/// ### get_realfile
|
||||||
|
|||||||
@@ -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
|
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||||
@@ -71,8 +75,8 @@ impl FileTransferActivity {
|
|||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => {
|
FileExplorerTab::Local => {
|
||||||
// Get selected entry
|
// Get selected entry
|
||||||
if self.local.files.get(self.local.index).is_some() {
|
if self.local.get_current_file().is_some() {
|
||||||
let entry: FsEntry = self.local.files.get(self.local.index).unwrap().clone();
|
let entry: FsEntry = self.local.get_current_file().unwrap().clone();
|
||||||
if let Some(ctx) = self.context.as_mut() {
|
if let Some(ctx) = self.context.as_mut() {
|
||||||
match ctx.local.copy(&entry, dest_path.as_path()) {
|
match ctx.local.copy(&entry, dest_path.as_path()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -104,8 +108,8 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
// Get selected entry
|
// Get selected entry
|
||||||
if self.remote.files.get(self.remote.index).is_some() {
|
if self.remote.get_current_file().is_some() {
|
||||||
let entry: FsEntry = self.remote.files.get(self.remote.index).unwrap().clone();
|
let entry: FsEntry = self.remote.get_current_file().unwrap().clone();
|
||||||
match self.client.as_mut().copy(&entry, dest_path.as_path()) {
|
match self.client.as_mut().copy(&entry, dest_path.as_path()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.log(
|
self.log(
|
||||||
@@ -205,7 +209,7 @@ impl FileTransferActivity {
|
|||||||
dst_path = wrkdir;
|
dst_path = wrkdir;
|
||||||
}
|
}
|
||||||
// Check if file entry exists
|
// 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();
|
let full_path: PathBuf = entry.get_abs_path();
|
||||||
// Rename file or directory and report status as popup
|
// Rename file or directory and report status as popup
|
||||||
match self
|
match self
|
||||||
@@ -245,7 +249,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
// Check if file entry exists
|
// 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();
|
let full_path: PathBuf = entry.get_abs_path();
|
||||||
// Rename file or directory and report status as popup
|
// Rename file or directory and report status as popup
|
||||||
let dst_path: PathBuf = PathBuf::from(input);
|
let dst_path: PathBuf = PathBuf::from(input);
|
||||||
@@ -289,7 +293,7 @@ impl FileTransferActivity {
|
|||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => {
|
FileExplorerTab::Local => {
|
||||||
// Check if file entry exists
|
// 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();
|
let full_path: PathBuf = entry.get_abs_path();
|
||||||
// Delete file or directory and report status as popup
|
// Delete file or directory and report status as popup
|
||||||
match self.context.as_mut().unwrap().local.remove(entry) {
|
match self.context.as_mut().unwrap().local.remove(entry) {
|
||||||
@@ -318,7 +322,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
// Check if file entry exists
|
// 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();
|
let full_path: PathBuf = entry.get_abs_path();
|
||||||
// Delete file
|
// Delete file
|
||||||
match self.client.remove(entry) {
|
match self.client.remove(entry) {
|
||||||
@@ -355,16 +359,16 @@ impl FileTransferActivity {
|
|||||||
// Get pwd
|
// Get pwd
|
||||||
let wrkdir: PathBuf = self.remote.wrkdir.clone();
|
let wrkdir: PathBuf = self.remote.wrkdir.clone();
|
||||||
// Get file and clone (due to mutable / immutable stuff...)
|
// Get file and clone (due to mutable / immutable stuff...)
|
||||||
if self.local.files.get(self.local.index).is_some() {
|
if self.local.get_current_file().is_some() {
|
||||||
let file: FsEntry = self.local.files.get(self.local.index).unwrap().clone();
|
let file: FsEntry = self.local.get_current_file().unwrap().clone();
|
||||||
// Call upload; pass realfile, keep link name
|
// Call upload; pass realfile, keep link name
|
||||||
self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(input));
|
self.filetransfer_send(&file.get_realfile(), wrkdir.as_path(), Some(input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
// Get file and clone (due to mutable / immutable stuff...)
|
// Get file and clone (due to mutable / immutable stuff...)
|
||||||
if self.remote.files.get(self.remote.index).is_some() {
|
if self.remote.get_current_file().is_some() {
|
||||||
let file: FsEntry = self.remote.files.get(self.remote.index).unwrap().clone();
|
let file: FsEntry = self.remote.get_current_file().unwrap().clone();
|
||||||
// Call upload; pass realfile, keep link name
|
// Call upload; pass realfile, keep link name
|
||||||
let wrkdir: PathBuf = self.local.wrkdir.clone();
|
let wrkdir: PathBuf = self.local.wrkdir.clone();
|
||||||
self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(input));
|
self.filetransfer_recv(&file.get_realfile(), wrkdir.as_path(), Some(input));
|
||||||
@@ -380,15 +384,19 @@ impl FileTransferActivity {
|
|||||||
match self.tab {
|
match self.tab {
|
||||||
FileExplorerTab::Local => {
|
FileExplorerTab::Local => {
|
||||||
// Check if file exists
|
// 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() {
|
if input == file.get_name() {
|
||||||
self.log_and_alert(
|
file_exists = true;
|
||||||
LogLevel::Warn,
|
|
||||||
format!("File \"{}\" already exists", input,),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if file_exists {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Warn,
|
||||||
|
format!("File \"{}\" already exists", input,),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Create file
|
// Create file
|
||||||
let file_path: PathBuf = PathBuf::from(input.as_str());
|
let file_path: PathBuf = PathBuf::from(input.as_str());
|
||||||
if let Some(ctx) = self.context.as_mut() {
|
if let Some(ctx) = self.context.as_mut() {
|
||||||
@@ -409,15 +417,19 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
// Check if file exists
|
// 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() {
|
if input == file.get_name() {
|
||||||
self.log_and_alert(
|
file_exists = true;
|
||||||
LogLevel::Warn,
|
|
||||||
format!("File \"{}\" already exists", input,),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if file_exists {
|
||||||
|
self.log_and_alert(
|
||||||
|
LogLevel::Warn,
|
||||||
|
format!("File \"{}\" already exists", input,),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Get path on remote
|
// Get path on remote
|
||||||
let file_path: PathBuf = PathBuf::from(input.as_str());
|
let file_path: PathBuf = PathBuf::from(input.as_str());
|
||||||
// Create file (on local)
|
// Create file (on local)
|
||||||
|
|||||||
532
src/ui/activities/filetransfer_activity/explorer.rs
Normal file
532
src/ui/activities/filetransfer_activity/explorer.rs
Normal 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
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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::Tab => self.switch_input_field(), // <TAB> switch tab
|
||||||
KeyCode::Right => self.tab = FileExplorerTab::Remote, // <RIGHT> switch to right tab
|
KeyCode::Right => self.tab = FileExplorerTab::Remote, // <RIGHT> switch to right tab
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
// Move index up; or move to the last element if 0
|
// Decrement index
|
||||||
self.local.index = match self.local.index {
|
self.local.decr_index();
|
||||||
0 => self.local.files.len() - 1,
|
|
||||||
_ => self.local.index - 1,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
// Move index down
|
// Increment index
|
||||||
if self.local.index + 1 < self.local.files.len() {
|
self.local.incr_index();
|
||||||
self.local.index += 1;
|
|
||||||
} else {
|
|
||||||
self.local.index = 0; // Move at the beginning of the list
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::PageUp => {
|
KeyCode::PageUp => {
|
||||||
// Move index up (fast)
|
// Decrement index by 8
|
||||||
if self.local.index > 8 {
|
self.local.decr_index_by(8);
|
||||||
self.local.index -= 8; // Decrease by `8` if possible
|
|
||||||
} else {
|
|
||||||
self.local.index = 0; // Set to 0 otherwise
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
// Move index down (fast)
|
// Increment index by 8
|
||||||
if self.local.index + 8 >= self.local.files.len() {
|
self.local.incr_index_by(8);
|
||||||
// If overflows, set to size
|
|
||||||
self.local.index = self.local.files.len() - 1;
|
|
||||||
} else {
|
|
||||||
self.local.index += 8; // Increase by `8`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// Match selected file
|
// Match selected file
|
||||||
let local_files: Vec<FsEntry> = self.local.files.clone();
|
let mut entry: Option<FsEntry> = None;
|
||||||
if let Some(entry) = local_files.get(self.local.index) {
|
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
|
// If directory, enter directory, otherwise check if symlink
|
||||||
match entry {
|
match entry {
|
||||||
FsEntry::Directory(dir) => {
|
FsEntry::Directory(dir) => {
|
||||||
@@ -162,7 +153,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
KeyCode::Delete => {
|
KeyCode::Delete => {
|
||||||
// Get file at index
|
// 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
|
// Get file name
|
||||||
let file_name: String = match entry {
|
let file_name: String = match entry {
|
||||||
FsEntry::Directory(dir) => dir.name.clone(),
|
FsEntry::Directory(dir) => dir.name.clone(),
|
||||||
@@ -177,6 +168,10 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char(ch) => match ch {
|
KeyCode::Char(ch) => match ch {
|
||||||
|
'a' | 'A' => {
|
||||||
|
// Toggle hidden files
|
||||||
|
self.local.toggle_hidden_files();
|
||||||
|
}
|
||||||
'c' | 'C' => {
|
'c' | 'C' => {
|
||||||
// Copy
|
// Copy
|
||||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
self.input_mode = InputMode::Popup(PopupType::Input(
|
||||||
@@ -193,7 +188,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
'e' | 'E' => {
|
'e' | 'E' => {
|
||||||
// Get file at index
|
// 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
|
// Get file name
|
||||||
let file_name: String = match entry {
|
let file_name: String = match entry {
|
||||||
FsEntry::Directory(dir) => dir.name.clone(),
|
FsEntry::Directory(dir) => dir.name.clone(),
|
||||||
@@ -237,10 +232,9 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
'o' | 'O' => {
|
'o' | 'O' => {
|
||||||
// Edit local file
|
// 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...
|
// Clone entry due to mutable stuff...
|
||||||
let fsentry: FsEntry =
|
let fsentry: FsEntry = self.local.get_current_file().unwrap().clone();
|
||||||
self.local.files.get(self.local.index).unwrap().clone();
|
|
||||||
// Check if file
|
// Check if file
|
||||||
if fsentry.is_file() {
|
if fsentry.is_file() {
|
||||||
self.log(
|
self.log(
|
||||||
@@ -294,9 +288,8 @@ impl FileTransferActivity {
|
|||||||
// Get pwd
|
// Get pwd
|
||||||
let wrkdir: PathBuf = self.remote.wrkdir.clone();
|
let wrkdir: PathBuf = self.remote.wrkdir.clone();
|
||||||
// Get file and clone (due to mutable / immutable stuff...)
|
// Get file and clone (due to mutable / immutable stuff...)
|
||||||
if self.local.files.get(self.local.index).is_some() {
|
if self.local.get_current_file().is_some() {
|
||||||
let file: FsEntry =
|
let file: FsEntry = self.local.get_current_file().unwrap().clone();
|
||||||
self.local.files.get(self.local.index).unwrap().clone();
|
|
||||||
let name: String = file.get_name().to_string();
|
let name: String = file.get_name().to_string();
|
||||||
// Call upload; pass realfile, keep link name
|
// Call upload; pass realfile, keep link name
|
||||||
self.filetransfer_send(
|
self.filetransfer_send(
|
||||||
@@ -328,41 +321,28 @@ impl FileTransferActivity {
|
|||||||
KeyCode::Tab => self.switch_input_field(), // <TAB> switch tab
|
KeyCode::Tab => self.switch_input_field(), // <TAB> switch tab
|
||||||
KeyCode::Left => self.tab = FileExplorerTab::Local, // <LEFT> switch to local tab
|
KeyCode::Left => self.tab = FileExplorerTab::Local, // <LEFT> switch to local tab
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
// Move index up; or move to the last element if 0
|
// Decrement index
|
||||||
self.remote.index = match self.remote.index {
|
self.remote.decr_index();
|
||||||
0 => self.remote.files.len() - 1,
|
|
||||||
_ => self.remote.index - 1,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
// Move index down
|
// Increment index
|
||||||
if self.remote.index + 1 < self.remote.files.len() {
|
self.remote.incr_index();
|
||||||
self.remote.index += 1;
|
|
||||||
} else {
|
|
||||||
self.remote.index = 0; // Move at the beginning of the list
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::PageUp => {
|
KeyCode::PageUp => {
|
||||||
// Move index up (fast)
|
// Decrement index by 8
|
||||||
if self.remote.index > 8 {
|
self.remote.decr_index_by(8);
|
||||||
self.remote.index -= 8; // Decrease by `8` if possible
|
|
||||||
} else {
|
|
||||||
self.remote.index = 0; // Set to 0 otherwise
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
// Move index down (fast)
|
// Increment index by 8
|
||||||
if self.remote.index + 8 >= self.remote.files.len() {
|
self.remote.incr_index_by(8);
|
||||||
// If overflows, set to size
|
|
||||||
self.remote.index = self.remote.files.len() - 1;
|
|
||||||
} else {
|
|
||||||
self.remote.index += 8; // Increase by `8`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// Match selected file
|
// Match selected file
|
||||||
let files: Vec<FsEntry> = self.remote.files.clone();
|
let mut entry: Option<FsEntry> = None;
|
||||||
if let Some(entry) = files.get(self.remote.index) {
|
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
|
// If directory, enter directory; if file, check if is symlink
|
||||||
match entry {
|
match entry {
|
||||||
FsEntry::Directory(dir) => {
|
FsEntry::Directory(dir) => {
|
||||||
@@ -388,7 +368,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
KeyCode::Delete => {
|
KeyCode::Delete => {
|
||||||
// Get file at index
|
// 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
|
// Get file name
|
||||||
let file_name: String = match entry {
|
let file_name: String = match entry {
|
||||||
FsEntry::Directory(dir) => dir.name.clone(),
|
FsEntry::Directory(dir) => dir.name.clone(),
|
||||||
@@ -403,6 +383,10 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char(ch) => match ch {
|
KeyCode::Char(ch) => match ch {
|
||||||
|
'a' | 'A' => {
|
||||||
|
// Toggle hidden files
|
||||||
|
self.remote.toggle_hidden_files();
|
||||||
|
}
|
||||||
'c' | 'C' => {
|
'c' | 'C' => {
|
||||||
// Copy
|
// Copy
|
||||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
self.input_mode = InputMode::Popup(PopupType::Input(
|
||||||
@@ -419,7 +403,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
'e' | 'E' => {
|
'e' | 'E' => {
|
||||||
// Get file at index
|
// 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
|
// Get file name
|
||||||
let file_name: String = match entry {
|
let file_name: String = match entry {
|
||||||
FsEntry::Directory(dir) => dir.name.clone(),
|
FsEntry::Directory(dir) => dir.name.clone(),
|
||||||
@@ -462,10 +446,9 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
'o' | 'O' => {
|
'o' | 'O' => {
|
||||||
// Edit remote file
|
// 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...
|
// Clone entry due to mutable stuff...
|
||||||
let fsentry: FsEntry =
|
let fsentry: FsEntry = self.remote.get_current_file().unwrap().clone();
|
||||||
self.remote.files.get(self.remote.index).unwrap().clone();
|
|
||||||
// Check if file
|
// Check if file
|
||||||
if let FsEntry::File(file) = fsentry {
|
if let FsEntry::File(file) = fsentry {
|
||||||
self.log(
|
self.log(
|
||||||
@@ -516,9 +499,8 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
' ' => {
|
' ' => {
|
||||||
// Get file and clone (due to mutable / immutable stuff...)
|
// Get file and clone (due to mutable / immutable stuff...)
|
||||||
if self.remote.files.get(self.remote.index).is_some() {
|
if self.remote.get_current_file().is_some() {
|
||||||
let file: FsEntry =
|
let file: FsEntry = self.remote.get_current_file().unwrap().clone();
|
||||||
self.remote.files.get(self.remote.index).unwrap().clone();
|
|
||||||
let name: String = file.get_name().to_string();
|
let name: String = file.get_name().to_string();
|
||||||
// Call upload; pass realfile, keep link name
|
// Call upload; pass realfile, keep link name
|
||||||
let wrkdir: PathBuf = self.local.wrkdir.clone();
|
let wrkdir: PathBuf = self.local.wrkdir.clone();
|
||||||
|
|||||||
@@ -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
|
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||||
@@ -70,10 +74,10 @@ impl FileTransferActivity {
|
|||||||
.split(chunks[0]);
|
.split(chunks[0]);
|
||||||
// Set localhost state
|
// Set localhost state
|
||||||
let mut localhost_state: ListState = ListState::default();
|
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
|
// Set remote state
|
||||||
let mut remote_state: ListState = ListState::default();
|
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
|
// Draw tabs
|
||||||
f.render_stateful_widget(
|
f.render_stateful_widget(
|
||||||
self.draw_local_explorer(tabs_chunks[0].width),
|
self.draw_local_explorer(tabs_chunks[0].width),
|
||||||
@@ -158,8 +162,7 @@ impl FileTransferActivity {
|
|||||||
};
|
};
|
||||||
let files: Vec<ListItem> = self
|
let files: Vec<ListItem> = self
|
||||||
.local
|
.local
|
||||||
.files
|
.iter_files()
|
||||||
.iter()
|
|
||||||
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
|
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
|
||||||
.collect();
|
.collect();
|
||||||
// Get colors to use; highlight element inverting fg/bg only when tab is active
|
// 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 {
|
pub(super) fn draw_remote_explorer(&self, width: u16) -> List {
|
||||||
let files: Vec<ListItem> = self
|
let files: Vec<ListItem> = self
|
||||||
.remote
|
.remote
|
||||||
.files
|
.iter_files()
|
||||||
.iter()
|
|
||||||
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
|
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
|
||||||
.collect();
|
.collect();
|
||||||
// Get colors to use; highlight element inverting fg/bg only when tab is active
|
// 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 {
|
let fsentry: Option<&FsEntry> = match self.tab {
|
||||||
FileExplorerTab::Local => {
|
FileExplorerTab::Local => {
|
||||||
// Get selected file
|
// Get selected file
|
||||||
match self.local.files.get(self.local.index) {
|
match self.local.get_current_file() {
|
||||||
Some(entry) => Some(entry),
|
Some(entry) => Some(entry),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileExplorerTab::Remote => match self.remote.files.get(self.remote.index) {
|
FileExplorerTab::Remote => match self.remote.get_current_file() {
|
||||||
Some(entry) => Some(entry),
|
Some(entry) => Some(entry),
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
@@ -715,6 +717,16 @@ impl FileTransferActivity {
|
|||||||
Span::raw(" "),
|
Span::raw(" "),
|
||||||
Span::raw("Delete file"),
|
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![
|
ListItem::new(Spans::from(vec![
|
||||||
Span::styled(
|
Span::styled(
|
||||||
"<C>",
|
"<C>",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
// This module is split into files, cause it's just too big
|
// This module is split into files, cause it's just too big
|
||||||
mod callbacks;
|
mod callbacks;
|
||||||
|
mod explorer;
|
||||||
mod input;
|
mod input;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod misc;
|
mod misc;
|
||||||
@@ -45,13 +46,14 @@ use crate::filetransfer::sftp_transfer::SftpFileTransfer;
|
|||||||
use crate::filetransfer::{FileTransfer, FileTransferProtocol};
|
use crate::filetransfer::{FileTransfer, FileTransferProtocol};
|
||||||
use crate::fs::FsEntry;
|
use crate::fs::FsEntry;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
|
use explorer::FileExplorer;
|
||||||
|
|
||||||
// Includes
|
// Includes
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use crossterm::event::Event as InputEvent;
|
use crossterm::event::Event as InputEvent;
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tui::style::Color;
|
use tui::style::Color;
|
||||||
|
|
||||||
@@ -113,59 +115,6 @@ enum InputMode {
|
|||||||
Popup(PopupType),
|
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
|
/// ## FileExplorerTab
|
||||||
///
|
///
|
||||||
/// File explorer tab
|
/// File explorer tab
|
||||||
@@ -312,18 +261,18 @@ impl FileTransferActivity {
|
|||||||
quit: false,
|
quit: false,
|
||||||
context: None,
|
context: None,
|
||||||
client: match protocol {
|
client: match protocol {
|
||||||
FileTransferProtocol::Sftp => {
|
FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new(
|
||||||
Box::new(SftpFileTransfer::new(Self::make_ssh_storage(config_client.as_ref())))
|
Self::make_ssh_storage(config_client.as_ref()),
|
||||||
}
|
)),
|
||||||
FileTransferProtocol::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)),
|
FileTransferProtocol::Ftp(ftps) => Box::new(FtpFileTransfer::new(ftps)),
|
||||||
FileTransferProtocol::Scp => {
|
FileTransferProtocol::Scp => Box::new(ScpFileTransfer::new(
|
||||||
Box::new(ScpFileTransfer::new(Self::make_ssh_storage(config_client.as_ref())))
|
Self::make_ssh_storage(config_client.as_ref()),
|
||||||
}
|
)),
|
||||||
},
|
},
|
||||||
config_cli: config_client,
|
config_cli: config_client,
|
||||||
params,
|
params,
|
||||||
local: FileExplorer::new(),
|
local: FileExplorer::new(16),
|
||||||
remote: FileExplorer::new(),
|
remote: FileExplorer::new(16),
|
||||||
tab: FileExplorerTab::Local,
|
tab: FileExplorerTab::Local,
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
|
||||||
@@ -335,7 +284,6 @@ impl FileTransferActivity {
|
|||||||
transfer: TransferStates::default(),
|
transfer: TransferStates::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
|
||||||
@@ -599,17 +603,17 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn local_scan(&mut self, path: &Path) {
|
pub(super) fn local_scan(&mut self, path: &Path) {
|
||||||
match self.context.as_ref().unwrap().local.scan_dir(path) {
|
match self.context.as_ref().unwrap().local.scan_dir(path) {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
self.local.files = files;
|
self.local.set_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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Sort files
|
// Sort files
|
||||||
self.local.sort_files_by_name();
|
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) => {
|
Err(err) => {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
@@ -626,17 +630,17 @@ impl FileTransferActivity {
|
|||||||
pub(super) fn remote_scan(&mut self, path: &Path) {
|
pub(super) fn remote_scan(&mut self, path: &Path) {
|
||||||
match self.client.list_dir(path) {
|
match self.client.list_dir(path) {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
self.remote.files = files;
|
self.remote.set_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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Sort files
|
// Sort files
|
||||||
self.remote.sort_files_by_name();
|
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) => {
|
Err(err) => {
|
||||||
self.log_and_alert(
|
self.log_and_alert(
|
||||||
@@ -663,7 +667,7 @@ impl FileTransferActivity {
|
|||||||
// Reload files
|
// Reload files
|
||||||
self.local_scan(path);
|
self.local_scan(path);
|
||||||
// Reset index
|
// Reset index
|
||||||
self.local.index = 0;
|
self.local.set_index(0);
|
||||||
// Set wrkdir
|
// Set wrkdir
|
||||||
self.local.wrkdir = PathBuf::from(path);
|
self.local.wrkdir = PathBuf::from(path);
|
||||||
// Push prev_dir to stack
|
// Push prev_dir to stack
|
||||||
@@ -694,7 +698,7 @@ impl FileTransferActivity {
|
|||||||
// Update files
|
// Update files
|
||||||
self.remote_scan(path);
|
self.remote_scan(path);
|
||||||
// Reset index
|
// Reset index
|
||||||
self.remote.index = 0;
|
self.remote.set_index(0);
|
||||||
// Set wrkdir
|
// Set wrkdir
|
||||||
self.remote.wrkdir = PathBuf::from(path);
|
self.remote.wrkdir = PathBuf::from(path);
|
||||||
// Push prev_dir to stack
|
// Push prev_dir to stack
|
||||||
|
|||||||
Reference in New Issue
Block a user