//! ## FileTransferActivity //! //! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall /** * MIT License * * termscp - Copyright (c) 2021 Christian Visintin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // This module is split into files, cause it's just too big mod actions; mod components; mod lib; mod misc; mod session; mod update; mod view; // locals use super::{Activity, Context, ExitReason}; use crate::config::themes::Theme; use crate::explorer::{FileExplorer, FileSorting}; use crate::filetransfer::{Builder, FileTransferParams}; use crate::host::Localhost; use crate::system::config_client::ConfigClient; pub(self) use lib::browser; use lib::browser::Browser; use lib::transfer::{TransferOpts, TransferStates}; pub(self) use session::TransferPayload; // Includes use chrono::{DateTime, Local}; use remotefs::RemoteFs; use std::collections::VecDeque; use std::time::Duration; use tempfile::TempDir; use tuirealm::{Application, EventListenerCfg, NoUserEvent}; // -- components #[derive(Debug, Eq, PartialEq, Clone, Hash)] enum Id { CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, ExplorerFind, ExplorerLocal, ExplorerRemote, FatalPopup, FileInfoPopup, FindPopup, FooterBar, GlobalListener, GotoPopup, KeybindingsPopup, Log, MkdirPopup, NewfilePopup, OpenWithPopup, ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup, ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote, SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, } #[derive(Debug, PartialEq)] enum Msg { PendingAction(PendingActionMsg), Transfer(TransferMsg), Ui(UiMsg), None, } #[derive(Debug, PartialEq)] enum PendingActionMsg { CloseReplacePopups, CloseSyncBrowsingMkdirPopup, MakePendingDirectory, TransferPendingFile, } #[derive(Debug, PartialEq)] enum TransferMsg { AbortTransfer, CopyFileTo(String), CreateSymlink(String), DeleteFile, EnterDirectory, ExecuteCmd(String), GoTo(String), GoToParentDirectory, GoToPreviousDirectory, Mkdir(String), NewFile(String), OpenFile, OpenFileWith(String), OpenTextFile, ReloadDir, RenameFile(String), SaveFileAs(String), SearchFile(String), TransferFile, } #[derive(Debug, PartialEq)] enum UiMsg { ChangeFileSorting(FileSorting), ChangeTransferWindow, CloseCopyPopup, CloseDeletePopup, CloseDisconnectPopup, CloseErrorPopup, CloseExecPopup, CloseFatalPopup, CloseFileInfoPopup, CloseFileSortingPopup, CloseFindExplorer, CloseFindPopup, CloseGotoPopup, CloseKeybindingsPopup, CloseMkdirPopup, CloseNewFilePopup, CloseOpenWithPopup, CloseQuitPopup, CloseRenamePopup, CloseSaveAsPopup, CloseSymlinkPopup, Disconnect, ExplorerBackTabbed, LogBackTabbed, Quit, ReplacePopupTabbed, ShowCopyPopup, ShowDeletePopup, ShowDisconnectPopup, ShowExecPopup, ShowFileInfoPopup, ShowFileSortingPopup, ShowFindPopup, ShowGotoPopup, ShowKeybindingsPopup, ShowMkdirPopup, ShowNewFilePopup, ShowOpenWithPopup, ShowQuitPopup, ShowRenamePopup, ShowSaveAsPopup, ShowSymlinkPopup, ToggleHiddenFiles, ToggleSyncBrowsing, WindowResized, } /// Log level type enum LogLevel { Error, Warn, Info, } /// Log record entry struct LogRecord { pub time: DateTime, pub level: LogLevel, pub msg: String, } impl LogRecord { /// Instantiates a new LogRecord pub fn new(level: LogLevel, msg: String) -> LogRecord { LogRecord { time: Local::now(), level, msg, } } } /// FileTransferActivity is the data holder for the file transfer activity pub struct FileTransferActivity { /// Exit reason exit_reason: Option, /// Context holder context: Option, /// Tui-realm application app: Application, /// Whether should redraw UI redraw: bool, /// Localhost bridge host: Localhost, /// Remote host client client: Box, /// Browser browser: Browser, /// Current log lines log_records: VecDeque, transfer: TransferStates, /// Temporary directory where to store temporary stuff cache: Option, } impl FileTransferActivity { /// Instantiates a new FileTransferActivity pub fn new(host: Localhost, params: &FileTransferParams, ticks: Duration) -> Self { // Get config client let config_client: ConfigClient = Self::init_config_client(); Self { exit_reason: None, context: None, app: Application::init( EventListenerCfg::default() .poll_timeout(ticks) .default_input_listener(ticks), ), redraw: true, host, client: Builder::build(params.protocol, params.params.clone(), &config_client), browser: Browser::new(&config_client), log_records: VecDeque::with_capacity(256), // 256 events is enough I guess transfer: TransferStates::default(), cache: match TempDir::new() { Ok(d) => Some(d), Err(_) => None, }, } } fn local(&self) -> &FileExplorer { self.browser.local() } fn local_mut(&mut self) -> &mut FileExplorer { self.browser.local_mut() } fn remote(&self) -> &FileExplorer { self.browser.remote() } fn remote_mut(&mut self) -> &mut FileExplorer { self.browser.remote_mut() } fn found(&self) -> Option<&FileExplorer> { self.browser.found() } fn found_mut(&mut self) -> Option<&mut FileExplorer> { self.browser.found_mut() } /// Get file name for a file in cache fn get_cache_tmp_name(&self, name: &str, file_type: Option<&str>) -> Option { self.cache.as_ref().map(|_| { let base: String = format!( "{}-{}", name, std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() ); match file_type { None => base, Some(file_type) => format!("{}.{}", base, file_type), } }) } /// Returns a reference to context fn context(&self) -> &Context { self.context.as_ref().unwrap() } /// Returns a mutable reference to context fn context_mut(&mut self) -> &mut Context { self.context.as_mut().unwrap() } /// Returns config client reference fn config(&self) -> &ConfigClient { self.context().config() } /// Get a reference to `Theme` fn theme(&self) -> &Theme { self.context().theme_provider().theme() } } /** * Activity Trait * Keep it clean :) * Use methods instead! */ impl Activity for FileTransferActivity { /// `on_create` is the function which must be called to initialize the activity. /// `on_create` must initialize all the data structures used by the activity fn on_create(&mut self, context: Context) { debug!("Initializing activity..."); // Set context self.context = Some(context); // Clear terminal if let Err(err) = self.context.as_mut().unwrap().terminal().clear_screen() { error!("Failed to clear screen: {}", err); } // Put raw mode on enabled if let Err(err) = self.context_mut().terminal().enable_raw_mode() { error!("Failed to enter raw mode: {}", err); } // Get files at current pwd self.reload_local_dir(); debug!("Read working directory"); // Configure text editor self.setup_text_editor(); debug!("Setup text editor"); // init view self.init(); debug!("Initialized view"); // Verify error state from context if let Some(err) = self.context.as_mut().unwrap().error() { error!("Fatal error on create: {}", err); self.mount_fatal(&err); } info!("Created FileTransferActivity"); } /// `on_draw` is the function which draws the graphical interface. /// This function must be called at each tick to refresh the interface fn on_draw(&mut self) { // Context must be something if self.context.is_none() { return; } // Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error) if !self.client.is_connected() && !self.app.mounted(&Id::FatalPopup) { let ftparams = self.context().ft_params().unwrap(); // print params let msg: String = Self::get_connection_msg(&ftparams.params); // Set init state to connecting popup self.mount_wait(msg.as_str()); // Force ui draw self.view(); // Connect to remote self.connect(); // Redraw self.redraw = true; } self.tick(); // View if self.redraw { self.view(); } } /// `will_umount` is the method which must be able to report to the activity manager, whether /// the activity should be terminated or not. /// If not, the call will return `None`, otherwise return`Some(ExitReason)` fn will_umount(&self) -> Option<&ExitReason> { self.exit_reason.as_ref() } /// `on_destroy` is the function which cleans up runtime variables and data before terminating the activity. /// This function must be called once before terminating the activity. fn on_destroy(&mut self) -> Option { // Destroy cache if let Some(cache) = self.cache.take() { if let Err(err) = cache.close() { error!("Failed to delete cache: {}", err); } } // Disable raw mode if let Err(err) = self.context_mut().terminal().disable_raw_mode() { error!("Failed to disable raw mode: {}", err); } if let Err(err) = self.context_mut().terminal().clear_screen() { error!("Failed to clear screen: {}", err); } // Disconnect client if self.client.is_connected() { let _ = self.client.disconnect(); } self.context.take() } }