summaryrefslogtreecommitdiffstats
path: root/src/opener.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/opener.rs')
-rw-r--r--src/opener.rs105
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)?;