Skip to content
119 linesCodeBlameRaw
1//! Updates. The app asks g1t.sh/downloads/desktop/latest.json soon after
2//! it starts and every six hours after, downloads a newer version in the
3//! background, and then says so: a notification, and the tray's "Restart
4//! to update". Nothing is installed under someone mid-sentence; the
5//! restart, or quitting, installs it. The manifest is signed with g1t's
6//! release key, built into the app, and a download whose signature does
7//! not check out is thrown away (scripts/desktop-release.mjs).
8
9use std::time::Duration;
10
11use tauri::AppHandle;
12use tauri::Manager;
13use tauri_plugin_notification::NotificationExt;
14use tauri_plugin_updater::{Update, UpdaterExt};
15
16use crate::{config, tray};
17
18/// An update downloaded and checked, waiting to be installed.
19pub struct Ready {
20 pub version: String,
21 update: Update,
22 bytes: Vec<u8>,
23}
24
25const FIRST_CHECK: Duration = Duration::from_secs(15);
26const EVERY: Duration = Duration::from_secs(6 * 60 * 60);
27
28/// Checks soon after the start, then every six hours, unless settings
29/// turned that off; a person can still check from the menu.
30pub fn schedule(app: AppHandle) {
31 tauri::async_runtime::spawn(async move {
32 tokio::time::sleep(FIRST_CHECK).await;
33 loop {
34 if config::settings(&app).auto_update {
35 let _ = check(&app, false).await;
36 }
37 tokio::time::sleep(EVERY).await;
38 }
39 });
40}
41
42/// The version downloaded and waiting, if one is.
43pub fn ready(app: &AppHandle) -> Option<String> {
44 app.state::<config::State>().update.lock().unwrap().as_ref().map(|ready| ready.version.clone())
45}
46
47fn tell(app: &AppHandle, title: &str, body: &str) {
48 let _ = app.notification().builder().title(title).body(body).show();
49}
50
51/// Looks for an update and downloads it. `asked` is a person asking from
52/// the tray or the site, who is told the answer either way; the timer
53/// says nothing unless there is something to install.
54pub async fn check(app: &AppHandle, asked: bool) -> Result<String, String> {
55 if let Some(version) = ready(app) {
56 if asked {
57 tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t to use it."));
58 }
59 return Ok(format!("ready:{version}"));
60 }
61 let updater = app.updater().map_err(|error| error.to_string())?;
62 let update = match updater.check().await {
63 Ok(Some(update)) => update,
64 Ok(None) => {
65 if asked {
66 tell(app, "g1t is up to date", &format!("You have version {}.", env!("CARGO_PKG_VERSION")));
67 }
68 return Ok("current".into());
69 }
70 Err(error) => {
71 if asked {
72 tell(app, "g1t could not check for updates", &error.to_string());
73 }
74 return Err(error.to_string());
75 }
76 };
77 let version = update.version.clone();
78 match update.download(|_, _| {}, || {}).await {
79 Ok(bytes) => {
80 *app.state::<config::State>().update.lock().unwrap() = Some(Ready { version: version.clone(), update, bytes });
81 tray::update_ready(&version);
82 tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t from the tray to use it."));
83 Ok(format!("ready:{version}"))
84 }
85 Err(error) => {
86 if asked {
87 tell(app, "The update could not be downloaded", &error.to_string());
88 }
89 Err(error.to_string())
90 }
91 }
92}
93
94/// Installs what is downloaded, if anything. On Windows the installer
95/// ends this process itself; elsewhere the caller goes on.
96fn install(app: &AppHandle) -> bool {
97 let ready = app.state::<config::State>().update.lock().unwrap().take();
98 let Some(ready) = ready else { return false };
99 match ready.update.install(ready.bytes) {
100 Ok(()) => true,
101 Err(error) => {
102 tell(app, "The update could not be installed", &error.to_string());
103 false
104 }
105 }
106}
107
108/// The tray's restart: installs the update and starts the new version.
109pub fn restart(app: &AppHandle) {
110 if install(app) {
111 app.restart();
112 }
113}
114
115/// Quit, installing a downloaded update on the way out.
116pub fn quit(app: &AppHandle) {
117 install(app);
118 app.exit(0);
119}