75 lines
2.0 KiB
Rust
75 lines
2.0 KiB
Rust
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() }),
|
|
}
|
|
}
|