Skip to main content

gpui_updater_core/
status.rs

1//! Backend-independent update state shared by the GPUI adapters.
2
3use std::fmt;
4
5use semver::Version;
6
7use crate::error::{Error, ErrorKind};
8
9/// Observable state of an update operation.
10///
11/// ```text
12/// Idle → Checking → UpToDate | Available(v)
13///                               → Downloading { downloaded, total } → Installing → Staged(v)
14/// any operation → Errored(error)
15/// ```
16///
17/// `Downloading` carries live byte counts. A cancelled download returns to
18/// `Available(v)`, not `Errored`.
19///
20/// Marked `#[non_exhaustive]`: later versions may add states, so match it
21/// with a wildcard arm.
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum UpdateStatus {
25    /// Nothing has been checked yet.
26    #[default]
27    Idle,
28    /// A check is in flight.
29    Checking,
30    /// The running version is the latest.
31    UpToDate,
32    /// A newer version is available to download and install.
33    Available(Version),
34    /// The update artifact is downloading. `total` is `None` until/unless the
35    /// server reports a `Content-Length`.
36    Downloading { downloaded: u64, total: Option<u64> },
37    /// The download is verified and being swapped into place.
38    Installing,
39    /// The update is installed and ready to launch on restart.
40    Staged(Version),
41    /// The last operation failed. [`UpdateError::kind`] says what kind of
42    /// failure it was; its `Display` is the message to show.
43    Errored(UpdateError),
44}
45
46impl UpdateStatus {
47    /// Whether an operation is currently in flight.
48    #[must_use]
49    pub fn is_busy(&self) -> bool {
50        matches!(
51            self,
52            Self::Checking | Self::Downloading { .. } | Self::Installing
53        )
54    }
55}
56
57/// A failed update operation, as the user interface sees it: what kind of
58/// failure it was and the message describing it.
59///
60/// Unlike [`Error`], this is cheap to clone and compare, so it can live in an
61/// [`UpdateStatus`]. Build one from an [`Error`] with `From`, or with
62/// [`UpdateError::new`] for failures an
63/// [`UpdateDriver`](crate::UpdateDriver) reports.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct UpdateError {
66    kind: ErrorKind,
67    message: String,
68}
69
70impl UpdateError {
71    /// A failure of `kind`, described by `message`.
72    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
73        Self {
74            kind,
75            message: message.into(),
76        }
77    }
78
79    /// What kind of failure this is.
80    #[must_use]
81    pub fn kind(&self) -> ErrorKind {
82        self.kind
83    }
84
85    /// The message describing the failure.
86    #[must_use]
87    pub fn message(&self) -> &str {
88        &self.message
89    }
90}
91
92impl fmt::Display for UpdateError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.write_str(&self.message)
95    }
96}
97
98impl std::error::Error for UpdateError {}
99
100impl From<&Error> for UpdateError {
101    fn from(error: &Error) -> Self {
102        Self::new(error.kind(), error.to_string())
103    }
104}
105
106impl From<Error> for UpdateError {
107    fn from(error: Error) -> Self {
108        Self::from(&error)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn keeps_the_kind_and_message_of_an_error() {
118        let error = UpdateError::from(Error::Http("GET x -> 503".into()));
119        assert_eq!(error.kind(), ErrorKind::Network);
120        assert_eq!(error.message(), "http error: GET x -> 503");
121        assert_eq!(error.to_string(), error.message());
122    }
123}