/*
*
* 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 .
*
*/
use super::{FileTransferActivity, FsEntry, InputMode, LogLevel, PopupType};
use std::io::{Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::time::Instant;
use tui::style::Color;
impl FileTransferActivity {
/// ### connect
///
/// Connect to remote
pub(super) fn connect(&mut self) {
// Connect to remote
match self.client.connect(
self.params.address.clone(),
self.params.port,
self.params.username.clone(),
self.params.password.clone(),
) {
Ok(welcome) => {
if let Some(banner) = welcome {
// Log welcome
self.log(
LogLevel::Info,
format!(
"Established connection with '{}': \"{}\"",
self.params.address, banner
)
.as_ref(),
);
}
// Set state to explorer
self.input_mode = InputMode::Explorer;
self.reload_remote_dir();
}
Err(err) => {
// Set popup fatal error
self.input_mode = InputMode::Popup(PopupType::Fatal(format!("{}", err)));
}
}
}
/// ### disconnect
///
/// disconnect from remote
pub(super) fn disconnect(&mut self) {
// Show popup disconnecting
self.input_mode = InputMode::Popup(PopupType::Alert(
Color::Red,
String::from("Disconnecting from remote..."),
));
// Disconnect
let _ = self.client.disconnect();
// Quit
self.disconnected = true;
}
/// ### disconnect_and_quit
///
/// disconnect from remote and then quit
pub(super) fn disconnect_and_quit(&mut self) {
self.disconnect();
self.quit = true;
}
/// ### reload_remote_dir
///
/// Reload remote directory entries
pub(super) fn reload_remote_dir(&mut self) {
// Get current entries
if let Ok(pwd) = self.client.pwd() {
self.remote_scan(pwd.as_path());
}
}
/// ### filetransfer_send
///
/// Send fs entry to remote.
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
pub(super) fn filetransfer_send(
&mut self,
entry: &FsEntry,
curr_remote_path: &Path,
dst_name: Option,
) {
// Write popup
let file_name: String = match entry {
FsEntry::Directory(dir) => dir.name.clone(),
FsEntry::File(file) => file.name.clone(),
};
self.input_mode = InputMode::Popup(PopupType::Wait(format!("Uploading \"{}\"", file_name)));
// Draw
self.draw();
// Get remote path
let mut remote_path: PathBuf = PathBuf::from(curr_remote_path);
let remote_file_name: PathBuf = match dst_name {
Some(s) => PathBuf::from(s.as_str()),
None => PathBuf::from(file_name.as_str()),
};
remote_path.push(remote_file_name);
// Match entry
match entry {
FsEntry::File(file) => {
// Upload file
// Try to open local file
match self
.context
.as_ref()
.unwrap()
.local
.open_file_read(file.abs_path.as_path())
{
Ok(mut fhnd) => match self.client.send_file(file, remote_path.as_path()) {
Ok(mut rhnd) => {
// Write file
let file_size: usize =
fhnd.seek(std::io::SeekFrom::End(0)).unwrap_or(0) as usize;
// rewind
if let Err(err) = fhnd.seek(std::io::SeekFrom::Start(0)) {
self.log(
LogLevel::Error,
format!("Could not rewind local file: {}", err).as_ref(),
);
}
// Write remote file
let mut total_bytes_written: usize = 0;
// Set input state to popup progress
self.input_mode = InputMode::Popup(PopupType::Progress(format!(
"Uploading \"{}\"",
file_name
)));
// Set started time
self.transfer_started = Instant::now();
let mut last_progress_val: f64 = 0.0;
loop {
// Read till you can
let mut buffer: [u8; 8192] = [0; 8192];
match fhnd.read(&mut buffer) {
Ok(bytes_read) => {
total_bytes_written += bytes_read;
if bytes_read == 0 {
break;
} else {
// Write bytes
if let Err(err) = rhnd.write(&buffer[0..bytes_read]) {
self.log(
LogLevel::Error,
format!("Could not write remote file: {}", err)
.as_ref(),
);
}
}
}
Err(err) => {
self.log(
LogLevel::Error,
format!("Could not read local file: {}", err).as_ref(),
);
}
}
// Increase progress
self.set_progress(total_bytes_written, file_size);
// Draw only if a significant progress has been made (performance improvement)
if last_progress_val + 0.5 >= self.transfer_progress {
// Draw
self.draw();
last_progress_val = self.transfer_progress;
}
}
// Finalize stream
if let Err(err) = self.client.on_sent(rhnd) {
self.log(
LogLevel::Warn,
format!("Could not finalize remote stream: \"{}\"", err)
.as_str(),
);
}
self.log(
LogLevel::Info,
format!(
"Saved file \"{}\" to \"{}\"",
file.abs_path.display(),
remote_path.display()
)
.as_ref(),
);
}
Err(err) => self.log(
LogLevel::Error,
format!(
"Failed to upload file \"{}\": {}",
file.abs_path.display(),
err
)
.as_ref(),
),
},
Err(err) => {
// Report error
self.log(
LogLevel::Error,
format!(
"Failed to open file \"{}\": {}",
file.abs_path.display(),
err
)
.as_ref(),
);
}
}
}
FsEntry::Directory(dir) => {
// Create directory on remote
match self.client.mkdir(remote_path.as_path()) {
Ok(_) => {
self.log(
LogLevel::Info,
format!("Created directory \"{}\"", remote_path.display()).as_ref(),
);
// Get files in dir
match self
.context
.as_ref()
.unwrap()
.local
.scan_dir(dir.abs_path.as_path())
{
Ok(entries) => {
// Iterate over files
for entry in entries.iter() {
// Send entry; name is always None after first call
self.filetransfer_send(&entry, remote_path.as_path(), None);
}
}
Err(err) => self.log(
LogLevel::Error,
format!(
"Could not scan directory \"{}\": {}",
dir.abs_path.display(),
err
)
.as_ref(),
),
}
}
Err(err) => self.log(
LogLevel::Error,
format!(
"Failed to create directory \"{}\": {}",
remote_path.display(),
err
)
.as_ref(),
),
}
}
}
// Scan dir on remote
if let Ok(path) = self.client.pwd() {
self.remote_scan(path.as_path());
}
// Eventually, Reset input mode to explorer
self.input_mode = InputMode::Explorer;
}
/// ### filetransfer_recv
///
/// Recv fs entry from remote.
/// If dst_name is Some, entry will be saved with a different name.
/// If entry is a directory, this applies to directory only
pub(super) fn filetransfer_recv(
&mut self,
entry: &FsEntry,
local_path: &Path,
dst_name: Option,
) {
// Write popup
let file_name: String = match entry {
FsEntry::Directory(dir) => dir.name.clone(),
FsEntry::File(file) => file.name.clone(),
};
self.input_mode =
InputMode::Popup(PopupType::Wait(format!("Downloading \"{}\"...", file_name)));
// Draw
self.draw();
// Match entry
match entry {
FsEntry::File(file) => {
// Get local file
let mut local_file_path: PathBuf = PathBuf::from(local_path);
let local_file_name: String = match dst_name {
Some(n) => n.clone(),
None => file.name.clone(),
};
local_file_path.push(local_file_name.as_str());
// Try to open local file
match self
.context
.as_ref()
.unwrap()
.local
.open_file_write(local_file_path.as_path())
{
Ok(mut local_file) => {
// Download file from remote
match self.client.recv_file(file) {
Ok(mut rhnd) => {
// Set popup progress
self.input_mode = InputMode::Popup(PopupType::Progress(format!(
"Downloading \"{}\"...",
file_name
)));
let mut total_bytes_written: usize = 0;
// Set started time
self.transfer_started = Instant::now();
// Write local file
let mut last_progress_val: f64 = 0.0;
loop {
// Read till you can
let mut buffer: [u8; 8192] = [0; 8192];
match rhnd.read(&mut buffer) {
Ok(bytes_read) => {
total_bytes_written += bytes_read;
if bytes_read == 0 {
break;
} else {
// Write bytes
if let Err(err) =
local_file.write(&buffer[0..bytes_read])
{
self.log(
LogLevel::Error,
format!(
"Could not write local file: {}",
err
)
.as_ref(),
);
}
}
}
Err(err) => self.log(
LogLevel::Error,
format!("Could not read remote file: {}", err).as_ref(),
),
}
// Set progress
self.set_progress(total_bytes_written, file.size);
// Draw only if a significant progress has been made (performance improvement)
if last_progress_val + 0.5 >= self.transfer_progress {
// Draw
self.draw();
last_progress_val = self.transfer_progress;
}
}
// Finalize stream
if let Err(err) = self.client.on_recv(rhnd) {
self.log(
LogLevel::Warn,
format!("Could not finalize remote stream: \"{}\"", err)
.as_str(),
);
}
// Log
self.log(
LogLevel::Info,
format!(
"Saved file \"{}\" to \"{}\"",
file.abs_path.display(),
local_file_path.display()
)
.as_ref(),
);
}
Err(err) => self.log(
LogLevel::Error,
format!(
"Failed to download file \"{}\": {}",
file.abs_path.display(),
err
)
.as_ref(),
),
}
}
Err(err) => {
// Report error
self.log(
LogLevel::Error,
format!(
"Failed to open local file for write \"{}\": {}",
local_file_path.display(),
err
)
.as_ref(),
);
}
}
}
FsEntry::Directory(dir) => {
// Get dir name
let mut local_dir_path: PathBuf = PathBuf::from(local_path);
match dst_name {
Some(name) => local_dir_path.push(name),
None => local_dir_path.push(dir.name.as_str()),
}
// Create directory on local
match self
.context
.as_mut()
.unwrap()
.local
.mkdir_ex(local_dir_path.as_path(), true)
{
Ok(_) => {
self.log(
LogLevel::Info,
format!("Created directory \"{}\"", local_dir_path.display()).as_ref(),
);
// Get files in dir
match self.client.list_dir(dir.abs_path.as_path()) {
Ok(entries) => {
// Iterate over files
for entry in entries.iter() {
// Receive entry; name is always None after first call
// Local path becomes local_dir_path
self.filetransfer_recv(&entry, local_dir_path.as_path(), None);
}
}
Err(err) => self.log(
LogLevel::Error,
format!(
"Could not scan directory \"{}\": {}",
dir.abs_path.display(),
err
)
.as_ref(),
),
}
}
Err(err) => self.log(
LogLevel::Error,
format!(
"Failed to create directory \"{}\": {}",
local_dir_path.display(),
err
)
.as_ref(),
),
}
}
}
// Reload directory on local
self.local_scan(local_path);
// Eventually, Reset input mode to explorer
self.input_mode = InputMode::Explorer;
}
/// ### local_scan
///
/// Scan current local directory
pub(super) fn local_scan(&mut self, path: &Path) {
match self.context.as_ref().unwrap().local.scan_dir(path) {
Ok(files) => {
// Reset index
self.local.index = 0;
self.local.files = files;
// Sort files
self.local.sort_files_by_name();
}
Err(err) => {
self.log(
LogLevel::Error,
format!("Could not scan current directory: {}", err).as_str(),
);
}
}
}
/// ### remote_scan
///
/// Scan current remote directory
pub(super) fn remote_scan(&mut self, path: &Path) {
match self.client.list_dir(path) {
Ok(files) => {
// Reset index
self.remote.index = 0;
self.remote.files = files;
// Sort files
self.remote.sort_files_by_name();
}
Err(err) => {
self.log(
LogLevel::Error,
format!("Could not scan current directory: {}", err).as_str(),
);
}
}
}
/// ### local_changedir
///
/// Change directory for local
pub(super) fn local_changedir(&mut self, path: &Path, push: bool) {
// Get current directory
let prev_dir: PathBuf = self.context.as_ref().unwrap().local.pwd();
// Change directory
match self
.context
.as_mut()
.unwrap()
.local
.change_wrkdir(PathBuf::from(path))
{
Ok(_) => {
self.log(
LogLevel::Info,
format!("Changed directory on local: {}", path.display()).as_str(),
);
// Reload files
self.local_scan(path);
// Push prev_dir to stack
if push {
self.local.pushd(prev_dir.as_path())
}
}
Err(err) => {
// Report err
self.input_mode = InputMode::Popup(PopupType::Alert(
Color::Red,
format!("Could not change working directory: {}", err),
));
}
}
}
pub(super) fn remote_changedir(&mut self, path: &Path, push: bool) {
// Get current directory
match self.client.pwd() {
Ok(prev_dir) => {
// Change directory
match self.client.change_dir(path) {
Ok(_) => {
self.log(
LogLevel::Info,
format!("Changed directory on remote: {}", path.display()).as_str(),
);
// Update files
self.remote_scan(path);
// Push prev_dir to stack
if push {
self.remote.pushd(prev_dir.as_path())
}
}
Err(err) => {
// Report err
self.input_mode = InputMode::Popup(PopupType::Alert(
Color::Red,
format!("Could not change working directory: {}", err),
));
}
}
}
Err(err) => {
// Report err
self.input_mode = InputMode::Popup(PopupType::Alert(
Color::Red,
format!("Could not change working directory: {}", err),
));
}
}
}
}