diff options
Diffstat (limited to 'src/status.rs')
-rw-r--r-- | src/status.rs | 309 |
1 files changed, 300 insertions, 9 deletions
diff --git a/src/status.rs b/src/status.rs index 393530e..bfcfe8f 100644 --- a/src/status.rs +++ b/src/status.rs @@ -17,21 +17,29 @@ use crate::args::Args; use crate::bulkrename::Bulk; use crate::cli_info::CliInfo; use crate::compress::Compresser; -use crate::config::Colors; +use crate::config::{Colors, Settings}; use crate::constant_strings_paths::TUIS_PATH; use crate::copy_move::{copy_move, CopyMove}; -use crate::cryptsetup::CryptoDeviceOpener; +use crate::cryptsetup::{BlockDeviceAction, CryptoDeviceOpener}; use crate::flagged::Flagged; -use crate::iso::IsoMounter; +use crate::iso::IsoDevice; use crate::marks::Marks; +use crate::mode::{InputSimple, Mode, NeedConfirmation}; +use crate::mount_help::MountHelper; use crate::opener::Opener; +use crate::password::{ + drop_sudo_privileges, execute_sudo_command_with_password, reset_sudo_faillock, PasswordHolder, + PasswordKind, PasswordUsage, +}; use crate::preview::{Directory, Preview}; +use crate::selectable_content::SelectableContent; use crate::shell_menu::{load_shell_menu, ShellMenu}; +use crate::shell_parser::ShellCommandParser; use crate::skim::Skimer; use crate::tab::Tab; use crate::term_manager::MIN_WIDTH_FOR_DUAL_PANE; use crate::trash::Trash; -use crate::utils::{disk_space, filename_from_path}; +use crate::utils::{current_username, disk_space, filename_from_path}; /// Holds every mutable parameter of the application itself, except for /// the "display" information. @@ -54,7 +62,7 @@ pub struct Status { /// Colors for extension // pub colors: ColorCache, /// terminal - term: Arc<Term>, + pub term: Arc<Term>, skimer: Skimer, /// do we display one or two tabs ? pub dual_pane: bool, @@ -72,7 +80,7 @@ pub struct Status { /// Encrypted devices opener pub encrypted_devices: CryptoDeviceOpener, /// Iso mounter. Set to None by default, dropped ASAP - pub iso_mounter: Option<IsoMounter>, + pub iso_device: Option<IsoDevice>, /// Compression methods pub compression: Compresser, /// NVIM RPC server address @@ -81,6 +89,9 @@ pub struct Status { pub bulk: Bulk, pub shell_menu: ShellMenu, pub cli_info: CliInfo, + pub start_folder: std::path::PathBuf, + pub password_holder: PasswordHolder, + pub sudo_command: Option<String>, } impl Status { @@ -96,6 +107,7 @@ impl Status { term: Arc<Term>, help: String, opener: Opener, + settings: &Settings, ) -> Result<Self> { let Ok(shell_menu) = load_shell_menu(TUIS_PATH) else { eprintln!("Couldn't load the TUIs config file at {TUIS_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/tuis.yaml for an example"); @@ -111,6 +123,7 @@ impl Status { let compression = Compresser::default(); let force_clear = false; let bulk = Bulk::default(); + let start_folder = std::fs::canonicalize(std::path::PathBuf::from(&args.path))?; // unsafe because of UsersCache::with_all_users let users_cache = unsafe { UsersCache::with_all_users() }; @@ -126,6 +139,8 @@ impl Status { .shortcut .extend_with_mount_points(&Self::disks_mounts(sys.disks())); let iso_mounter = None; + let password_holder = PasswordHolder::default(); + let sudo_command = None; Ok(Self { tabs: [left_tab, right_tab], @@ -134,10 +149,10 @@ impl Status { marks: Marks::read_from_config_file(), skimer: Skimer::new(term.clone()), term, - dual_pane: true, + dual_pane: settings.dual, preview_second: false, system_info: sys, - display_full: true, + display_full: settings.full, opener, help, trash, @@ -147,8 +162,11 @@ impl Status { force_clear, bulk, shell_menu, - iso_mounter, + iso_device: iso_mounter, cli_info, + start_folder, + password_holder, + sudo_command, }) } @@ -431,6 +449,279 @@ impl Status { pub fn force_clear(&mut self) { self.force_clear = true; } + + /// Mount the currently selected file (which should be an .iso file) to + /// `/run/media/$CURRENT_USER/fm_iso` + /// Ask a sudo password first if needed. It should always be the case. + pub fn mount_iso_drive(&mut self) -> Result<()> { + let path = self + .selected_non_mut() + .path_content + .selected_path_string() + .context("Couldn't parse the path")?; + if self.iso_device.is_none() { + self.iso_device = Some(IsoDevice::from_path(path)); + } + if let Some(ref mut iso_device) = self.iso_device { + if !self.password_holder.has_sudo() { + Self::ask_password( + self, + PasswordKind::SUDO, + Some(BlockDeviceAction::MOUNT), + PasswordUsage::ISO, + )?; + } else { + if iso_device.mount(¤t_username()?, &mut self.password_holder)? { + info!("iso mounter mounted {iso_device:?}"); + info!( + target: "special", + "iso :\n{}", + iso_device.as_string()?, + ); + let path = iso_device.mountpoints.clone().context("no mount point")?; + self.selected().set_pathcontent(&path)?; + }; + self.iso_device = None; + }; + } + Ok(()) + } + + /// Currently unused. + /// Umount an iso device. + pub fn umount_iso_drive(&mut self) -> Result<()> { + if let Some(ref mut iso_device) = self.iso_device { + if !self.password_holder.has_sudo() { + Self::ask_password( + self, + PasswordKind::SUDO, + Some(BlockDeviceAction::UMOUNT), + PasswordUsage::ISO, + )?; + } else { + iso_device.umount(¤t_username()?, &mut self.password_holder)?; + }; + } + Ok(()) + } + + /// Mount the selected encrypted device. Will ask first for sudo password and + /// passphrase. + /// Those passwords are always dropped immediatly after the commands are run. + pub fn mount_encrypted_drive(&mut self) -> Result<()> { + if !self.password_holder.has_sudo() { + Self::ask_password( + self, + PasswordKind::SUDO, + Some(BlockDeviceAction::MOUNT), + PasswordUsage::CRYPTSETUP, + ) + } else if !self.password_holder.has_cryptsetup() { + Self::ask_password( + self, + PasswordKind::CRYPTSETUP, + Some(BlockDeviceAction::MOUNT), + PasswordUsage::CRYPTSETUP, + ) + } else { + self.encrypted_devices + .mount_selected(&mut self.password_holder) + } + } + + /// Move to the selected crypted device mount point. + pub fn move_to_encrypted_drive(&mut self) -> Result<()> { + let Some(device) = self.encrypted_devices.selected() else { return Ok(()) }; + let Some(mount_point) = device.mount_point() else { return Ok(())}; + let tab = self.selected(); + let path = std::path::PathBuf::from(mount_point); + tab.history.push(&path); + tab.set_pathcontent(&path)?; + tab.refresh_view() + } + + /// Unmount the selected device. + /// Will ask first for a sudo password which is immediatly forgotten. + pub fn umount_encrypted_drive(&mut self) -> Result<()> { + if !self.password_holder.has_sudo() { + Self::ask_password( + self, + PasswordKind::SUDO, + Some(BlockDeviceAction::UMOUNT), + PasswordUsage::CRYPTSETUP, + ) + } else { + self.encrypted_devices + .umount_selected(&mut self.password_holder) + } + } + + /// Ask for a password of some kind (sudo or device passphrase). + pub fn ask_password( + &mut self, + password_kind: PasswordKind, + encrypted_action: Option<BlockDeviceAction>, + password_dest: PasswordUsage, + ) -> Result<()> { + info!("event ask password"); + self.selected() + .set_mode(Mode::InputSimple(InputSimple::Password( + password_kind, + encrypted_action, + password_dest, + ))); + Ok(()) + } + + /// Execute a new mark, saving it to a config file for futher use. + pub fn marks_new(&mut self, c: char, colors: &Colors) -> Result<()> { + let path = self.selected().path_content.path.clone(); + self.marks.new_mark(c, path)?; + { + let tab: &mut Tab = self.selected(); + tab.refresh_view() + }?; + self.selected().reset_mode(); + self.refresh_status(colors) + } + + /// Execute a jump to a mark, moving to a valid path. + /// If the saved path is invalid, it does nothing but reset the view. + pub fn marks_jump_char(&mut self, c: char, colors: &Colors) -> Result<()> { + if let Some(path) = self.marks.get(c) { + self.selected().set_pathcontent(&path)?; + self.selected().history.push(&path); + } + self.selected().refresh_view()?; + self.selected().reset_mode(); + self.refresh_status(colors) + } + + /// Reset the selected tab view to the default. + pub fn refresh_status(&mut self, colors: &Colors) -> Result<()> { + self.force_clear(); + self.refresh_users()?; + self.selected().refresh_view()?; + if let Mode::Tree = self.selected_non_mut().mode { + self.selected().make_tree(colors)? + } + Ok(()) + } + + /// When a rezise event occurs, we may hide the second panel if the width + /// isn't sufficiant to display enough information. + /// We also need to know the new height of the terminal to start scrolling + /// up or down. + pub fn resize(&mut self, width: usize, height: usize, colors: &Colors) -> Result<()> { + self.set_dual_pane_if_wide_enough(width)?; + self.selected().set_height(height); + self.refresh_status(colors)?; + Ok(()) + } + + /// Recursively delete all flagged files. + pub fn confirm_delete_files(&mut self, colors: &Colors) -> Result<()> { + for pathbuf in self.flagged.content.iter() { + if pathbuf.is_dir() { + std::fs::remove_dir_all(pathbuf)?; + } else { + std::fs::remove_file(pathbuf)?; + } + } + self.selected().reset_mode(); + self.clear_flags_and_reset_view()?; + self.refresh_status(colors) + } + + /// Empty the trash folder permanently. + pub fn confirm_trash_empty(&mut self) -> Result<()> { + self.trash.empty_trash()?; + self.selected().reset_mode(); + self.clear_flags_and_reset_view()?; + Ok(()) + } + + fn run_sudo_command(&mut self, colors: &Colors) -> Result<()> { + self.selected().set_mode(Mode::Normal); + reset_sudo_faillock()?; + let Some(sudo_command) = &self.sudo_command else { return Ok(()); }; + let args = ShellCommandParser::new(sudo_command).compute(self)?; + if args.is_empty() { + return Ok(()); + } + execute_sudo_command_with_password( + &args[1..], + &self.password_holder.sudo()?, + self.selected_non_mut().directory_of_selected()?, + )?; + self.password_holder.reset(); + drop_sudo_privileges()?; + self.refresh_status(colors) + } + + pub fn dispatch_password( + &mut self, + dest: PasswordUsage, + action: Option<BlockDeviceAction>, + colors: &Colors, + ) -> Result<()> { + match dest { + PasswordUsage::ISO => match action { + Some(BlockDeviceAction::MOUNT) => self.mount_iso_drive(), + Some(BlockDeviceAction::UMOUNT) => self.umount_iso_drive(), + None => Ok(()), + }, + PasswordUsage::CRYPTSETUP => match action { + Some(BlockDeviceAction::MOUNT) => self.mount_encrypted_drive(), + Some(BlockDeviceAction::UMOUNT) => self.umount_encrypted_drive(), + None => Ok(()), + }, + PasswordUsage::SUDOCOMMAND => Self::run_sudo_command(self, colors), + } + } + + pub fn read_nvim_listen_address_if_needed(&mut self) { + if !self.nvim_server.is_empty() { + return; + } + let Ok(nvim_listen_address) = std::env::var("NVIM_LISTEN_ADDRESS") else { return; }; + self.nvim_server = nvim_listen_address; + } + + /// Execute a command requiring a confirmation (Delete, Move or Copy). + pub fn confirm_action( + &mut self, + confirmed_action: NeedConfirmation, + colors: &Colors, + ) -> Result<()> { + self.match_confirmed_mode(confirmed_action, colors)?; + self.selected().reset_mode(); + Ok(()) + } + + fn match_confirmed_mode( + &mut self, + confirmed_action: NeedConfirmation, + colors: &Colors, + ) -> Result<()> { + match confirmed_action { + NeedConfirmation::Delete => self.confirm_delete_files(colors), + NeedConfirmation::Move => self.cut_or_copy_flagged_files(CopyMove::Move), + NeedConfirmation::Copy => self.cut_or_copy_flagged_files(CopyMove::Copy), + NeedConfirmation::EmptyTrash => self.confirm_trash_empty(), + } + } + + /// Select the left or right tab depending on where the user clicked. + pub fn select_pane(&mut self, col: u16) -> Result<()> { + let (width, _) = self.term_size()?; + if (col as usize) < width / 2 { + self.select_tab(0)?; + } else { + self.select_tab(1)?; + }; + Ok(()) + } } fn parse_keyname(keyname: &str) -> Option<String> { |