#[cfg(any(target_os = "macos", test))]
use std::path::Path;
use std::path::PathBuf;
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InstallKind {
MacApp(PathBuf),
AppImage(PathBuf),
Unknown,
}
impl InstallKind {
pub fn is_in_place(&self) -> bool {
matches!(self, InstallKind::MacApp(_) | InstallKind::AppImage(_))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UpdateStage {
Downloading { done: u64, total: u64 },
Preparing,
Installing,
Verifying,
}
impl UpdateStage {
pub fn label(&self) -> &'static str {
match self {
UpdateStage::Downloading { .. } => "Downloading update…",
UpdateStage::Preparing => "Preparing…",
UpdateStage::Installing => "Installing…",
UpdateStage::Verifying => "Verifying…",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Relaunch {
Current,
Binary(PathBuf),
}
#[cfg(any(target_os = "macos", test))]
pub(crate) fn bundle_of(exe: &Path) -> Option<PathBuf> {
exe
.ancestors()
.nth(3)
.filter(|p| p.extension().is_some_and(|e| e == "app"))
.map(|p| p.to_path_buf())
}
pub fn detect() -> InstallKind {
if let Some(image) = std::env::var_os("APPIMAGE") {
return InstallKind::AppImage(PathBuf::from(image));
}
#[cfg(target_os = "macos")]
{
let exe = std::env::current_exe().unwrap_or_default();
if let Some(app) = bundle_of(&exe) {
return InstallKind::MacApp(app);
}
}
InstallKind::Unknown
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_swappable_installs_update_in_place() {
assert!(InstallKind::MacApp(PathBuf::from("/Applications/Acme.app")).is_in_place());
assert!(InstallKind::AppImage(PathBuf::from("/x/Acme.AppImage")).is_in_place());
assert!(!InstallKind::Unknown.is_in_place());
}
#[test]
fn bundle_is_three_levels_above_the_executable() {
assert_eq!(
bundle_of(Path::new("/Applications/Acme.app/Contents/MacOS/acme")),
Some(PathBuf::from("/Applications/Acme.app"))
);
}
#[test]
fn unbundled_executables_have_no_bundle() {
assert_eq!(bundle_of(Path::new("/dev/acme/target/release/acme")), None);
assert_eq!(bundle_of(Path::new("/usr/local/bin/acme")), None);
assert_eq!(bundle_of(Path::new("acme")), None);
}
#[test]
fn every_stage_has_a_label() {
for stage in [
UpdateStage::Downloading { done: 0, total: 0 },
UpdateStage::Preparing,
UpdateStage::Installing,
UpdateStage::Verifying,
] {
assert!(!stage.label().is_empty());
}
}
}