Skip to main content

objdiff_core/jobs/
update.rs

1use std::{
2    env::{current_dir, current_exe},
3    fs::File,
4    path::PathBuf,
5    sync::mpsc::Receiver,
6    task::Waker,
7};
8
9use anyhow::{Context, Result};
10pub use self_update; // Re-export self_update crate
11use self_update::update::ReleaseUpdate;
12
13use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
14
15pub struct UpdateConfig {
16    pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
17    pub bin_name: String,
18}
19
20pub struct UpdateResult {
21    pub exe_path: PathBuf,
22}
23
24fn run_update(
25    status: &JobContext,
26    cancel: Receiver<()>,
27    config: UpdateConfig,
28) -> Result<Box<UpdateResult>> {
29    update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
30    let updater = (config.build_updater)().context("Failed to create release updater")?;
31    let releases = updater.get_latest_release()?;
32    let latest_release = releases.latest().context("No release found")?;
33    let asset =
34        latest_release.assets().iter().find(|a| a.name() == config.bin_name).ok_or_else(|| {
35            anyhow::Error::msg(format!("No release asset for {}", config.bin_name))
36        })?;
37
38    update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
39    let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
40    let tmp_path = tmp_dir.path().join(asset.name());
41    let tmp_file = File::create(&tmp_path)?;
42    self_update::Download::from_url(asset.download_url())
43        .request_header(self_update::http::header::ACCEPT, "application/octet-stream")
44        .download_to(tmp_file)?;
45
46    update_status(status, "Extracting release".to_string(), 2, 3, &cancel)?;
47    let tmp_file = tmp_dir.path().join("replacement_tmp");
48    let target_file = current_exe()?;
49    self_update::Move::from_source(&tmp_path)
50        .replace_using_temp(&tmp_file)
51        .to_dest(&target_file)?;
52    #[cfg(unix)]
53    {
54        use std::{fs, os::unix::fs::PermissionsExt};
55        fs::set_permissions(&target_file, fs::Permissions::from_mode(0o755))?;
56    }
57    tmp_dir.close()?;
58
59    update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
60    Ok(Box::from(UpdateResult { exe_path: target_file }))
61}
62
63pub fn start_update(waker: Waker, config: UpdateConfig) -> JobState {
64    start_job(waker, "Update app", Job::Update, move |context, cancel| {
65        run_update(&context, cancel, config).map(JobResult::Update)
66    })
67}