Skip to main content

node_gui/
update.rs

1//! Manual and background update checks.
2
3use std::{fmt::Display, thread};
4
5use wowlab_node::{check_for_update, install_update};
6
7use crate::{AppWindow, VERSION};
8
9#[derive(Debug, Eq, PartialEq)]
10pub(crate) enum CommandSuccess {
11    Available(String),
12    Current,
13    Installed,
14}
15
16#[derive(Debug, thiserror::Error)]
17pub(crate) enum CommandError {
18    #[error("Failed to check for updates: {0}")]
19    Check(#[source] wowlab_node::UpdateError),
20    #[error("Update failed: {0}")]
21    Install(#[source] wowlab_node::UpdateError),
22}
23
24pub(crate) fn run_command(check: bool) -> Result<CommandSuccess, CommandError> {
25    if check {
26        match check_for_update(VERSION) {
27            Ok(Some(version)) => Ok(CommandSuccess::Available(version)),
28            Ok(None) => Ok(CommandSuccess::Current),
29            Err(error) => Err(CommandError::Check(error)),
30        }
31    } else {
32        match install_update("node-gui", VERSION) {
33            Ok(_) => Ok(CommandSuccess::Installed),
34            Err(error) => Err(CommandError::Install(error)),
35        }
36    }
37}
38
39pub(crate) const fn should_check_on_startup(no_update: bool) -> bool {
40    !no_update
41}
42
43pub(crate) fn check_in_background(window: &slint::Weak<AppWindow>) {
44    if let Some(window) = window.upgrade() {
45        window.set_update_status("Checking...".into());
46    }
47
48    let worker_window = window.clone();
49    let worker = thread::Builder::new()
50        .name("node-update-check".to_string())
51        .spawn(move || {
52            let status = check_status(check_for_update(VERSION));
53            let _ = slint::invoke_from_event_loop(move || {
54                if let Some(window) = worker_window.upgrade() {
55                    window.set_update_status(status.into());
56                }
57            });
58        });
59
60    if let Err(error) = worker {
61        set_status(window, format!("Check failed: {error}"));
62    }
63}
64
65fn set_status(window: &slint::Weak<AppWindow>, status: String) {
66    if let Some(window) = window.upgrade() {
67        window.set_update_status(status.into());
68    }
69}
70
71fn check_status<E>(result: Result<Option<String>, E>) -> String
72where
73    E: Display,
74{
75    match result {
76        Ok(Some(version)) => format!("Update available: v{version}"),
77        Ok(None) => "Already on latest version".to_string(),
78        Err(error) => format!("Check failed: {error}"),
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use googletest::prelude::*;
85
86    use super::*;
87
88    #[gtest]
89    fn startup_policy_respects_no_update_flag() -> Result<()> {
90        verify_eq!(
91            [
92                should_check_on_startup(false),
93                should_check_on_startup(true)
94            ],
95            [true, false]
96        )
97    }
98
99    #[gtest]
100    fn background_status_preserves_user_facing_messages() -> Result<()> {
101        verify_eq!(
102            [
103                check_status::<&str>(Ok(Some("1.2.3".to_string()))),
104                check_status::<&str>(Ok(None)),
105                check_status::<&str>(Err("offline")),
106            ],
107            [
108                "Update available: v1.2.3",
109                "Already on latest version",
110                "Check failed: offline",
111            ]
112        )
113    }
114}