Skip to main content

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}