use std::{ fs, io, path::{Path, PathBuf}, process::{Command, ExitStatus}, }; use thiserror::Error; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DownloadFileAction { Open, Reveal, } #[derive(Debug, Error)] pub enum DownloadFileError { #[error("download file is unavailable: {path:?}")] FileUnavailable { path: PathBuf }, #[error("failed to launch {action} for download file: {path:?}: {source}")] LaunchFailed { action: &'static str, path: PathBuf, source: io::Error }, #[error("{action} failed for download file: {path:?} ({status})")] CommandFailed { action: &'static str, path: PathBuf, status: ExitStatus }, } impl DownloadFileAction { pub fn run(self, path: &Path) -> Result<(), DownloadFileError> { ensure_regular_file(path)?; let status = self.command(path).status().map_err(|source| DownloadFileError::LaunchFailed { action: self.label(), path: path.to_path_buf(), source, })?; if status.success() { return Ok(()); } Err(DownloadFileError::CommandFailed { action: self.label(), path: path.to_path_buf(), status, }) } fn command(self, path: &Path) -> Command { let mut command = Command::new("/usr/bin/open"); match self { Self::Open => { command.arg(path); } Self::Reveal => { command.arg("-R").arg(path); } } command } pub fn label(self) -> &'static str { match self { Self::Open => "open", Self::Reveal => "reveal", } } } fn ensure_regular_file(path: &Path) -> Result<(), DownloadFileError> { match fs::metadata(path) { Ok(metadata) if metadata.is_file() => Ok(()), Ok(_) | Err(_) => Err(DownloadFileError::FileUnavailable { path: path.to_path_buf() }), } }