diff options
Diffstat (limited to 'src/opener.rs')
-rw-r--r-- | src/opener.rs | 105 |
1 files changed, 78 insertions, 27 deletions
diff --git a/src/opener.rs b/src/opener.rs index 024d369..46ad71d 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -1,10 +1,14 @@ use std::collections::HashMap; use std::env; +use std::fmt; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use anyhow::{anyhow, Context, Result}; use log::info; use serde_yaml; +use strum::IntoEnumIterator; +use strum_macros::{Display, EnumIter, EnumString}; use crate::constant_strings_paths::{ DEFAULT_AUDIO_OPENER, DEFAULT_IMAGE_OPENER, DEFAULT_OFFICE_OPENER, DEFAULT_OPENER, @@ -12,7 +16,6 @@ use crate::constant_strings_paths::{ }; use crate::decompress::{decompress_gz, decompress_xz, decompress_zip}; use crate::fileinfo::extract_extension; -use crate::fm_error::{FmError, FmResult}; fn find_it<P>(exe_name: P) -> Option<PathBuf> where @@ -31,8 +34,9 @@ where } /// Different kind of extensions for default openers. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] +#[derive(Clone, Hash, Eq, PartialEq, Debug, Display, Default, EnumString, EnumIter)] pub enum ExtensionKind { + #[default] Audio, Bitmap, Office, @@ -47,7 +51,7 @@ pub enum ExtensionKind { // TODO: move those associations to a config file impl ExtensionKind { fn parse(ext: &str) -> Self { - match ext { + match ext.to_lowercase().as_str() { "avif" | "bmp" | "gif" | "png" | "jpg" | "jpeg" | "pgm" | "ppm" | "webp" | "tiff" => { Self::Bitmap } @@ -77,6 +81,14 @@ impl ExtensionKind { "lzip" | "lzma" | "rar" | "tgz" | "gz" | "bzip2" => { Self::Internal(InternalVariant::DecompressGz) } + // iso files can't be mounted without more information than we hold in this enum : + // we need to be able to change the status of the application to ask for a sudo password. + // we can't use the "basic" opener to mount them. + // ATM this is the only extension we can't open, it may change in the future. + "iso" => { + info!("extension kind iso"); + Self::Internal(InternalVariant::NotSupported) + } _ => Self::Default, } } @@ -137,9 +149,30 @@ impl OpenerAssociation { OpenerInfo::internal(ExtensionKind::Internal(InternalVariant::DecompressXz)) .unwrap(), ), + ( + ExtensionKind::Internal(InternalVariant::NotSupported), + OpenerInfo::internal(ExtensionKind::Internal(InternalVariant::NotSupported)) + .unwrap(), + ), ]), } } + + /// Converts itself into an hashmap of strings. + /// Used to include openers in the help + pub fn as_map_of_strings(&self) -> std::collections::HashMap<String, String> { + let mut associations: std::collections::HashMap<String, String> = self + .association + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + + for s in ExtensionKind::iter() { + let s = s.to_string(); + associations.entry(s).or_insert_with(|| "".to_owned()); + } + associations + } } macro_rules! open_file_with { @@ -183,11 +216,13 @@ impl OpenerAssociation { /// Some kind of files are "opened" using internal methods. /// ATM only one kind of files is supported, compressed ones, which use /// libarchive internally. -#[derive(Clone, Hash, PartialEq, Eq, Debug)] +#[derive(Clone, Hash, PartialEq, Eq, Debug, Default)] pub enum InternalVariant { + #[default] DecompressZip, DecompressXz, DecompressGz, + NotSupported, } /// A way to open one kind of files. @@ -212,16 +247,15 @@ impl OpenerInfo { } } - fn internal(extension_kind: ExtensionKind) -> FmResult<Self> { + fn internal(extension_kind: ExtensionKind) -> Result<Self> { match extension_kind { ExtensionKind::Internal(internal) => Ok(Self { external_program: None, internal_variant: Some(internal), use_term: false, }), - _ => Err(FmError::custom( - "internal", - &format!("unsupported extension_kind: {extension_kind:?}"), + _ => Err(anyhow!( + "internal: unsupported extension_kind: {extension_kind:?}" )), } } @@ -234,6 +268,17 @@ impl OpenerInfo { } } +impl fmt::Display for OpenerInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + let s = if let Some(external) = &self.external_program { + external + } else { + "" + }; + write!(f, "{s}") + } +} + /// Holds the associations between different kind of files and opener method /// as well as the name of the terminal configured by the user. #[derive(Clone)] @@ -268,9 +313,9 @@ impl Opener { /// It may fail if the program changed after reading the config file. /// It may also fail if the program can't handle this kind of files. /// This is quite a tricky method, there's many possible failures. - pub fn open(&self, filepath: &Path) -> FmResult<()> { + pub fn open(&self, filepath: &Path) -> Result<()> { if filepath.is_dir() { - return Err(FmError::custom("open", "Can't execute a directory")); + return Err(anyhow!("open! can't execute a directory")); } let extension = extract_extension(filepath); let open_info = self.get_opener(extension); @@ -285,11 +330,21 @@ impl Opener { InternalVariant::DecompressZip => decompress_zip(filepath)?, InternalVariant::DecompressXz => decompress_xz(filepath)?, InternalVariant::DecompressGz => decompress_gz(filepath)?, + InternalVariant::NotSupported => (), }; } Ok(()) } + /// Returns the open info about this file. + /// It's used to check if the file can be opened without specific actions or not. + /// This opener can't mutate the status and can't ask for a sudo password. + /// Some files requires root to be opened (ie. ISO files which are mounted). + pub fn open_info(&self, filepath: &Path) -> &OpenerInfo { + let extension = extract_extension(filepath); + self.get_opener(extension) + } + /// Open a file with a given program. /// If the program requires a terminal, the terminal itself is opened /// and the program and its parameters are sent to it. @@ -298,10 +353,10 @@ impl Opener { program: &str, use_term: bool, filepath: &std::path::Path, - ) -> FmResult<std::process::Child> { + ) -> Result<std::process::Child> { let strpath = filepath .to_str() - .ok_or_else(|| FmError::custom("open with", "Can't parse filepath to str"))?; + .context("open with: can't parse filepath to str")?; let args = vec![program, strpath]; if use_term { self.open_terminal(args) @@ -314,13 +369,13 @@ impl Opener { self.opener_association.update_from_file(yaml) } - fn open_directly(&self, mut args: Vec<&str>) -> FmResult<std::process::Child> { + fn open_directly(&self, mut args: Vec<&str>) -> Result<std::process::Child> { let executable = args.remove(0); execute_in_child(executable, &args) } // TODO: use terminal specific parameters instead of -e for all terminals - fn open_terminal(&self, mut args: Vec<&str>) -> FmResult<std::process::Child> { + fn open_terminal(&self, mut args: Vec<&str>) -> Result<std::process::Child> { args.insert(0, "-e"); execute_in_child(&self.terminal, &args) } @@ -333,7 +388,7 @@ impl Opener { /// Execute a command with options in a fork. /// Returns an handle to the child process. -pub fn execute_in_child(exe: &str, args: &Vec<&str>) -> FmResult<std::process::Child> { +pub fn execute_in_child(exe: &str, args: &Vec<&str>) -> Result<std::process::Child> { info!("execute_in_child. executable: {exe}, arguments: {args:?}",); Ok(Command::new(exe).args(args).spawn()?) } @@ -341,10 +396,7 @@ pub fn execute_in_child(exe: &str, args: &Vec<&str>) -> FmResult<std::process::C /// Execute a command with options in a fork. /// Returns an handle to the child process. /// Branch stdin, stderr and stdout to /dev/null -pub fn execute_in_child_without_output( - exe: &str, - args: &Vec<&str>, -) -> FmResult<std::process::Child> { +pub fn execute_in_child_without_output(exe: &str, args: &Vec<&str>) -> Result<std::process::Child> { info!("execute_in_child_without_output. executable: {exe}, arguments: {args:?}",); Ok(Command::new(exe) .args(args) @@ -358,7 +410,7 @@ pub fn execute_in_child_without_output_with_path<P>( exe: &str, path: P, args: Option<&Vec<&str>>, -) -> FmResult<std::process::Child> +) -> Result<std::process::Child> where P: AsRef<Path>, { @@ -378,9 +430,9 @@ where /// Execute a command with options in a fork. /// Wait for termination and return either : /// `Ok(stdout)` if the status code is 0 -/// or `Err(FmError::custom(..., ...))` otherwise. +/// an Error otherwise /// Branch stdin and stderr to /dev/null -pub fn execute_and_capture_output(exe: &str, args: &Vec<&str>) -> FmResult<String> { +pub fn execute_and_capture_output(exe: &str, args: &Vec<&str>) -> Result<String> { info!("execute_and_capture_output. executable: {exe}, arguments: {args:?}",); let child = Command::new(exe) .args(args) @@ -392,9 +444,8 @@ pub fn execute_and_capture_output(exe: &str, args: &Vec<&str>) -> FmResult<Strin if output.status.success() { Ok(String::from_utf8(output.stdout)?) } else { - Err(FmError::custom( - "execute_and_capture_output", - "Command didn't finished correctly", + Err(anyhow!( + "execute_and_capture_output: command didn't finished correctly", )) } } @@ -402,7 +453,7 @@ pub fn execute_and_capture_output(exe: &str, args: &Vec<&str>) -> FmResult<Strin /// Execute a command with options in a fork. /// Wait for termination and return either `Ok(stdout)`. /// Branch stdin and stderr to /dev/null -pub fn execute_and_capture_output_without_check(exe: &str, args: &Vec<&str>) -> FmResult<String> { +pub fn execute_and_capture_output_without_check(exe: &str, args: &Vec<&str>) -> Result<String> { info!("execute_and_capture_output_without_check. executable: {exe}, arguments: {args:?}",); let child = Command::new(exe) .args(args) @@ -417,7 +468,7 @@ pub fn execute_and_capture_output_without_check(exe: &str, args: &Vec<&str>) -> /// Returns the opener created from opener file with the given terminal /// application name. /// It may fail if the file can't be read. -pub fn load_opener(path: &str, terminal: &str) -> FmResult<Opener> { +pub fn load_opener(path: &str, terminal: &str) -> Result<Opener> { let mut opener = Opener::new(terminal); let file = std::fs::File::open(std::path::Path::new(&shellexpand::tilde(path).to_string()))?; let yaml = serde_yaml::from_reader(file)?; |