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}