gpui_updater_core/driver.rs
1//! The contract for handing the whole update flow to an external updater.
2//!
3//! [`UpdateEngine`](crate::UpdateEngine) owns every step itself: it fetches
4//! metadata, downloads, verifies, and installs. Some platforms already have a
5//! mature updater that does all of that, and more: Sparkle on macOS installs
6//! into `/Applications` for non-admin users, installs on quit, and applies
7//! delta updates. An [`UpdateDriver`] wraps such an updater so the GPUI
8//! `Updater` entity can present it with the same `UpdateStatus` and the same
9//! `check` / `download_and_install` / `restart` calls.
10//!
11//! The driver reports what it is doing through [`DriverEvents`], a cheap,
12//! thread-safe handle the driver receives in [`UpdateDriver::start`]. Events
13//! are queued and applied on the GPUI foreground, so a driver may emit from any
14//! thread and from inside its own callbacks without re-entering the app.
15//!
16//! This module does not depend on GPUI, so a driver crate (such as
17//! `gpui-updater-sparkle`) builds and tests without it.
18
19use std::any::Any;
20use std::fmt;
21use std::sync::Arc;
22
23use semver::Version;
24
25use crate::release::Release;
26use crate::status::UpdateError;
27
28/// An external updater that runs the whole check → download → install →
29/// relaunch flow by itself.
30///
31/// Drivers are owned by the GPUI `Updater` entity, which calls every method on
32/// the foreground (main) thread. They need not be `Send`: Sparkle objects, for
33/// example, may only be touched from the main thread.
34///
35/// The `Any` supertrait lets the adapters hand the concrete driver back (their
36/// `Updater::driver_mut`) for driver-specific settings.
37pub trait UpdateDriver: Any {
38 /// Start the driver. Called exactly once, before any other method.
39 ///
40 /// Keep `events` and report progress through it for as long as the driver
41 /// lives.
42 fn start(&mut self, events: DriverEvents);
43
44 /// The user asked to check for updates now.
45 fn check(&mut self);
46
47 /// The user asked to install the update the driver last reported with
48 /// [`DriverEvent::Found`].
49 fn install(&mut self);
50
51 /// The user asked to relaunch into the update the driver last reported with
52 /// [`DriverEvent::ReadyToRelaunch`].
53 fn relaunch(&mut self);
54
55 /// The user asked to stop the download in progress. Report
56 /// [`DriverEvent::Settled`] once it has stopped.
57 ///
58 /// The default does nothing: not every external updater can stop a
59 /// download from outside its own interface.
60 fn cancel(&mut self) {}
61}
62
63/// Something an [`UpdateDriver`] wants the app to know.
64///
65/// Each event maps onto the GPUI `UpdateStatus` of the same name, except
66/// [`Settled`](Self::Settled), which tells the `Updater` that nothing is in
67/// flight any more.
68#[derive(Debug, Clone)]
69#[non_exhaustive]
70#[allow(
71 clippy::large_enum_variant,
72 reason = "events are rare and short-lived; boxing `Found` would only add API churn"
73)]
74pub enum DriverEvent {
75 /// A check started.
76 Checking,
77 /// The check finished and the running version is the latest.
78 UpToDate,
79 /// The check found a newer release.
80 Found(Release),
81 /// The update is downloading. `total` is `None` when the size is unknown;
82 /// drivers that cannot observe byte progress report
83 /// `{ downloaded: 0, total: None }`.
84 Downloading {
85 /// Bytes downloaded so far.
86 downloaded: u64,
87 /// Expected total size in bytes, when known.
88 total: Option<u64>,
89 },
90 /// The update is being verified, extracted, or installed.
91 Installing,
92 /// The update is staged; relaunching will run `version`.
93 ReadyToRelaunch(Version),
94 /// The current operation failed. The error's message is shown to the
95 /// user; its kind tells the app what kind of failure it was.
96 Failed(UpdateError),
97 /// The driver finished its session (for example, the user dismissed an
98 /// update prompt or cancelled a download). Nothing is in flight any more.
99 Settled,
100}
101
102/// Where an [`UpdateDriver`] sends its [`DriverEvent`]s.
103///
104/// Cloning is cheap and the handle is `Send + Sync`, so it can move into
105/// callbacks and background threads.
106#[derive(Clone)]
107pub struct DriverEvents {
108 sink: Arc<dyn Fn(DriverEvent) + Send + Sync>,
109}
110
111impl DriverEvents {
112 /// Wrap a function that receives every emitted event.
113 ///
114 /// The GPUI `Updater` builds its own handle; call this to test a driver or
115 /// to drive one without GPUI.
116 pub fn new(sink: impl Fn(DriverEvent) + Send + Sync + 'static) -> Self {
117 Self {
118 sink: Arc::new(sink),
119 }
120 }
121
122 /// Report an event.
123 pub fn emit(&self, event: DriverEvent) {
124 (self.sink)(event);
125 }
126}
127
128impl fmt::Debug for DriverEvents {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.debug_struct("DriverEvents").finish_non_exhaustive()
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use std::sync::Mutex;
137
138 use super::*;
139
140 #[test]
141 fn events_reach_the_sink_from_any_clone() {
142 let seen = Arc::new(Mutex::new(Vec::new()));
143 let events = {
144 let seen = seen.clone();
145 DriverEvents::new(move |event| seen.lock().unwrap().push(format!("{event:?}")))
146 };
147 let clone = events.clone();
148 std::thread::spawn(move || clone.emit(DriverEvent::Checking))
149 .join()
150 .unwrap();
151 events.emit(DriverEvent::Settled);
152 assert_eq!(*seen.lock().unwrap(), ["Checking", "Settled"]);
153 }
154}