Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| preparing for cloud work | 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 | ||
| 9 | use std::time::Duration; | |
| 10 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 11 | use tauri::{AppHandle, Emitter, Manager}; |
| preparing for cloud work | 12 | use tauri_plugin_notification::NotificationExt; |
| 13 | use tauri_plugin_updater::{Update, UpdaterExt}; | |
| 14 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 15 | use crate::{config, settings, tray}; |
| preparing for cloud work | 16 | |
| 17 | /// An update downloaded and checked, waiting to be installed. | |
| 18 | pub struct Ready { | |
| 19 | pub version: String, | |
| 20 | update: Update, | |
| 21 | bytes: Vec<u8>, | |
| 22 | } | |
| 23 | ||
| 24 | const FIRST_CHECK: Duration = Duration::from_secs(15); | |
| 25 | const EVERY: Duration = Duration::from_secs(6 * 60 * 60); | |
| 26 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 27 | /// Checks soon after the start, then every six hours, unless settings |
| 28 | /// turned that off; a person can still check from the menu. | |
| preparing for cloud work | 29 | pub fn schedule(app: AppHandle) { |
| 30 | tauri::async_runtime::spawn(async move { | |
| 31 | tokio::time::sleep(FIRST_CHECK).await; | |
| 32 | loop { | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 33 | if config::settings(&app).auto_update { |
| 34 | let _ = check(&app, false).await; | |
| 35 | } | |
| preparing for cloud work | 36 | tokio::time::sleep(EVERY).await; |
| 37 | } | |
| 38 | }); | |
| 39 | } | |
| 40 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 41 | /// When the updater last heard back, in milliseconds since 1970. |
| 42 | pub fn checked_at(app: &AppHandle) -> Option<u64> { | |
| 43 | *app.state::<config::State>().checked_at.lock().unwrap() | |
| 44 | } | |
| 45 | ||
| 46 | fn heard_back(app: &AppHandle) { | |
| 47 | let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|since| since.as_millis() as u64).ok(); | |
| 48 | *app.state::<config::State>().checked_at.lock().unwrap() = now; | |
| 49 | } | |
| 50 | ||
| preparing for cloud work | 51 | /// The version downloaded and waiting, if one is. |
| 52 | pub fn ready(app: &AppHandle) -> Option<String> { | |
| 53 | app.state::<config::State>().update.lock().unwrap().as_ref().map(|ready| ready.version.clone()) | |
| 54 | } | |
| 55 | ||
| 56 | fn tell(app: &AppHandle, title: &str, body: &str) { | |
| 57 | let _ = app.notification().builder().title(title).body(body).show(); | |
| 58 | } | |
| 59 | ||
| 60 | /// Looks for an update and downloads it. `asked` is a person asking from | |
| 61 | /// the tray or the site, who is told the answer either way; the timer | |
| 62 | /// says nothing unless there is something to install. | |
| 63 | pub async fn check(app: &AppHandle, asked: bool) -> Result<String, String> { | |
| 64 | if let Some(version) = ready(app) { | |
| 65 | if asked { | |
| 66 | tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t to use it.")); | |
| 67 | } | |
| 68 | return Ok(format!("ready:{version}")); | |
| 69 | } | |
| 70 | let updater = app.updater().map_err(|error| error.to_string())?; | |
| 71 | let update = match updater.check().await { | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 72 | Ok(Some(update)) => { |
| 73 | heard_back(app); | |
| 74 | update | |
| 75 | } | |
| preparing for cloud work | 76 | Ok(None) => { |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 77 | heard_back(app); |
| preparing for cloud work | 78 | if asked { |
| 79 | tell(app, "g1t is up to date", &format!("You have version {}.", env!("CARGO_PKG_VERSION"))); | |
| 80 | } | |
| 81 | return Ok("current".into()); | |
| 82 | } | |
| 83 | Err(error) => { | |
| 84 | if asked { | |
| 85 | tell(app, "g1t could not check for updates", &error.to_string()); | |
| 86 | } | |
| 87 | return Err(error.to_string()); | |
| 88 | } | |
| 89 | }; | |
| 90 | let version = update.version.clone(); | |
| 91 | match update.download(|_, _| {}, || {}).await { | |
| 92 | Ok(bytes) => { | |
| 93 | *app.state::<config::State>().update.lock().unwrap() = Some(Ready { version: version.clone(), update, bytes }); | |
| 94 | tray::update_ready(&version); | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 95 | let _ = app.emit_to(settings::SETTINGS, settings::READY_EVENT, version.clone()); |
| preparing for cloud work | 96 | tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t from the tray to use it.")); |
| 97 | Ok(format!("ready:{version}")) | |
| 98 | } | |
| 99 | Err(error) => { | |
| 100 | if asked { | |
| 101 | tell(app, "The update could not be downloaded", &error.to_string()); | |
| 102 | } | |
| 103 | Err(error.to_string()) | |
| 104 | } | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | /// Installs what is downloaded, if anything. On Windows the installer | |
| 109 | /// ends this process itself; elsewhere the caller goes on. | |
| 110 | fn install(app: &AppHandle) -> bool { | |
| 111 | let ready = app.state::<config::State>().update.lock().unwrap().take(); | |
| 112 | let Some(ready) = ready else { return false }; | |
| 113 | match ready.update.install(ready.bytes) { | |
| 114 | Ok(()) => true, | |
| 115 | Err(error) => { | |
| 116 | tell(app, "The update could not be installed", &error.to_string()); | |
| 117 | false | |
| 118 | } | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | /// The tray's restart: installs the update and starts the new version. | |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 123 | /// The runner stops first, since on Windows the installer ends g1t. |
| preparing for cloud work | 124 | pub fn restart(app: &AppHandle) { |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 125 | if ready(app).is_none() { |
| 126 | return; | |
| 127 | } | |
| 128 | crate::runner::shutdown(app); | |
| preparing for cloud work | 129 | if install(app) { |
| 130 | app.restart(); | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 134 | /// Quit, stopping the runner and installing a downloaded update on the way out. |
| preparing for cloud work | 135 | pub fn quit(app: &AppHandle) { |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 136 | crate::runner::shutdown(app); |
| preparing for cloud work | 137 | install(app); |
| 138 | app.exit(0); | |
| 139 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.