use std::{
env::{self, current_dir, current_exe},
path::PathBuf,
};
use crate::{errors::*, AxoUpdater, ReleaseSource};
use axoasset::SourceFile;
use axotag::Version;
use camino::Utf8PathBuf;
use serde::Deserialize;
fn default_as_true() -> bool {
true
}
#[derive(Clone, Debug, Deserialize)]
pub struct InstallReceipt {
pub install_prefix: Utf8PathBuf,
#[allow(dead_code)]
pub binaries: Vec<String>,
#[serde(default = "Vec::new")]
#[allow(dead_code)]
pub cdylibs: Vec<String>,
pub source: ReleaseSource,
pub version: String,
pub provider: ReceiptProvider,
#[serde(default = "default_as_true")]
pub modify_path: bool,
}
#[derive(Clone, Debug, Deserialize)]
pub struct ReceiptProvider {
pub source: String,
pub version: String,
}
impl AxoUpdater {
pub fn load_receipt(&mut self) -> AxoupdateResult<&mut AxoUpdater> {
let Some(app_name) = self.name.clone() else {
return Err(AxoupdateError::NoAppNamePassed {});
};
self.load_receipt_as(&app_name)
}
pub fn load_receipt_as(&mut self, app_name: &str) -> AxoupdateResult<&mut AxoUpdater> {
let receipt = load_receipt_for(app_name)?;
self.source = Some(receipt.source);
self.current_version = Some(receipt.version.parse::<Version>()?);
let provider = crate::Provider {
source: receipt.provider.source,
version: receipt.provider.version.parse::<Version>()?,
};
self.current_version_installed_by = Some(provider);
self.install_prefix = Some(receipt.install_prefix);
self.modify_path = receipt.modify_path;
Ok(self)
}
pub fn check_receipt_is_for_this_executable(&self) -> AxoupdateResult<bool> {
let current_exe_path = Utf8PathBuf::from_path_buf(current_exe()?.canonicalize()?)
.map_err(|path| AxoupdateError::CaminoConversionFailed { path })?;
let mut current_exe_root = if let Some(parent) = current_exe_path.parent() {
parent.to_path_buf()
} else {
current_exe_path
};
let receipt_root = self.install_prefix_root_normalized()?;
if current_exe_root.file_name() == Some("bin") && receipt_root.file_name() != Some("bin") {
if let Some(parent) = current_exe_root.parent() {
current_exe_root = parent.to_path_buf();
}
}
if current_exe_root != receipt_root {
return Ok(false);
}
Ok(true)
}
}
pub(crate) fn get_config_paths(app_name: &str) -> AxoupdateResult<Vec<Utf8PathBuf>> {
let mut potential_homes = vec![];
if env::var("AXOUPDATER_CONFIG_WORKING_DIR").is_ok() {
Ok(vec![Utf8PathBuf::try_from(current_dir()?)?])
} else if let Ok(path) = env::var("AXOUPDATER_CONFIG_PATH") {
Ok(vec![Utf8PathBuf::from(path)])
} else {
let xdg_home = env::var("XDG_CONFIG_HOME")
.ok()
.map(Utf8PathBuf::from)
.map(|h| h.join(app_name));
if let Some(home) = &xdg_home {
if home.exists() {
potential_homes.push(home.to_owned());
}
}
let home = if cfg!(windows) {
env::var("LOCALAPPDATA")
.map(PathBuf::from)
.map(|h| h.join(app_name))
.ok()
} else {
homedir::my_home()?.map(|path| path.join(".config").join(app_name))
};
if let Some(home) = home {
potential_homes.push(Utf8PathBuf::try_from(home)?);
}
if potential_homes.is_empty() {
return Err(AxoupdateError::NoHome {});
}
Ok(potential_homes)
}
}
pub(crate) fn get_receipt_path(app_name: &str) -> AxoupdateResult<Option<Utf8PathBuf>> {
for receipt_prefix in get_config_paths(app_name)? {
let install_receipt_path = receipt_prefix.join(format!("{app_name}-receipt.json"));
if install_receipt_path.exists() {
return Ok(Some(install_receipt_path));
}
}
Ok(None)
}
fn load_receipt_from_path(install_receipt_path: &Utf8PathBuf) -> AxoupdateResult<InstallReceipt> {
Ok(SourceFile::load_local(install_receipt_path)?.deserialize_json()?)
}
fn load_receipt_for(app_name: &str) -> AxoupdateResult<InstallReceipt> {
let Some(install_receipt_path) = get_receipt_path(app_name)? else {
return Err(AxoupdateError::NoReceipt {
app_name: app_name.to_owned(),
});
};
load_receipt_from_path(&install_receipt_path).map_err(|_| AxoupdateError::ReceiptLoadFailed {
app_name: app_name.to_owned(),
})
}