auto_update_helper.rs

  1#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
  2
  3#[cfg(target_os = "windows")]
  4mod dialog;
  5#[cfg(target_os = "windows")]
  6mod updater;
  7
  8#[cfg(target_os = "windows")]
  9fn main() {
 10    if let Err(e) = windows_impl::run() {
 11        log::error!("Error: Zed update failed, {:?}", e);
 12        windows_impl::show_error(format!("Error: {:?}", e));
 13    }
 14}
 15
 16#[cfg(not(target_os = "windows"))]
 17fn main() {}
 18
 19#[cfg(target_os = "windows")]
 20mod windows_impl {
 21    use std::path::Path;
 22
 23    use super::dialog::create_dialog_window;
 24    use super::updater::perform_update;
 25    use anyhow::{Context as _, Result};
 26    use windows::{
 27        Win32::{
 28            Foundation::{HWND, LPARAM, WPARAM},
 29            UI::WindowsAndMessaging::{
 30                DispatchMessageW, GetMessageW, MB_ICONERROR, MB_SYSTEMMODAL, MSG, MessageBoxW,
 31                PostMessageW, WM_USER,
 32            },
 33        },
 34        core::HSTRING,
 35    };
 36
 37    pub(crate) const WM_JOB_UPDATED: u32 = WM_USER + 1;
 38    pub(crate) const WM_TERMINATE: u32 = WM_USER + 2;
 39
 40    #[derive(Debug)]
 41    struct Args {
 42        launch: Option<bool>,
 43    }
 44
 45    pub(crate) fn run() -> Result<()> {
 46        let helper_dir = std::env::current_exe()?
 47            .parent()
 48            .context("No parent directory")?
 49            .to_path_buf();
 50        init_log(&helper_dir)?;
 51        let app_dir = helper_dir
 52            .parent()
 53            .context("No parent directory")?
 54            .to_path_buf();
 55
 56        log::info!("======= Starting Zed update =======");
 57        let (tx, rx) = std::sync::mpsc::channel();
 58        let hwnd = create_dialog_window(rx)?.0 as isize;
 59        let args = parse_args();
 60        std::thread::spawn(move || {
 61            let result = perform_update(app_dir.as_path(), Some(hwnd), args.launch.unwrap_or(true));
 62            tx.send(result).ok();
 63            unsafe { PostMessageW(Some(HWND(hwnd as _)), WM_TERMINATE, WPARAM(0), LPARAM(0)) }.ok();
 64        });
 65        unsafe {
 66            let mut message = MSG::default();
 67            while GetMessageW(&mut message, None, 0, 0).as_bool() {
 68                DispatchMessageW(&message);
 69            }
 70        }
 71        Ok(())
 72    }
 73
 74    fn init_log(helper_dir: &Path) -> Result<()> {
 75        simplelog::WriteLogger::init(
 76            simplelog::LevelFilter::Info,
 77            simplelog::Config::default(),
 78            std::fs::File::options()
 79                .append(true)
 80                .create(true)
 81                .open(helper_dir.join("auto_update_helper.log"))?,
 82        )?;
 83        Ok(())
 84    }
 85
 86    fn parse_args() -> Args {
 87        let mut result = Args { launch: None };
 88        if let Some(candidate) = std::env::args().nth(1) {
 89            parse_single_arg(&candidate, &mut result);
 90        }
 91
 92        result
 93    }
 94
 95    fn parse_single_arg(arg: &str, result: &mut Args) {
 96        let Some((key, value)) = arg.strip_prefix("--").and_then(|arg| arg.split_once('=')) else {
 97            log::error!(
 98                "Invalid argument format: '{}'. Expected format: --key=value",
 99                arg
100            );
101            return;
102        };
103
104        match key {
105            "launch" => parse_launch_arg(value, &mut result.launch),
106            _ => log::error!("Unknown argument: --{}", key),
107        }
108    }
109
110    fn parse_launch_arg(value: &str, arg: &mut Option<bool>) {
111        match value {
112            "true" => *arg = Some(true),
113            "false" => *arg = Some(false),
114            _ => log::error!(
115                "Invalid value for --launch: '{}'. Expected 'true' or 'false'",
116                value
117            ),
118        }
119    }
120
121    pub(crate) fn show_error(mut content: String) {
122        if content.len() > 600 {
123            content.truncate(600);
124            content.push_str("...\n");
125        }
126        let _ = unsafe {
127            MessageBoxW(
128                None,
129                &HSTRING::from(content),
130                windows::core::w!("Error: Zed update failed."),
131                MB_ICONERROR | MB_SYSTEMMODAL,
132            )
133        };
134    }
135
136    #[cfg(test)]
137    mod tests {
138        use crate::windows_impl::{Args, parse_launch_arg, parse_single_arg};
139
140        #[test]
141        fn test_parse_launch_arg() {
142            let mut arg = None;
143            parse_launch_arg("true", &mut arg);
144            assert_eq!(arg, Some(true));
145
146            let mut arg = None;
147            parse_launch_arg("false", &mut arg);
148            assert_eq!(arg, Some(false));
149
150            let mut arg = None;
151            parse_launch_arg("invalid", &mut arg);
152            assert_eq!(arg, None);
153        }
154
155        #[test]
156        fn test_parse_single_arg() {
157            let mut args = Args { launch: None };
158            parse_single_arg("--launch=true", &mut args);
159            assert_eq!(args.launch, Some(true));
160
161            let mut args = Args { launch: None };
162            parse_single_arg("--launch=false", &mut args);
163            assert_eq!(args.launch, Some(false));
164
165            let mut args = Args { launch: None };
166            parse_single_arg("--launch=invalid", &mut args);
167            assert_eq!(args.launch, None);
168
169            let mut args = Args { launch: None };
170            parse_single_arg("--launch", &mut args);
171            assert_eq!(args.launch, None);
172
173            let mut args = Args { launch: None };
174            parse_single_arg("--unknown", &mut args);
175            assert_eq!(args.launch, None);
176        }
177    }
178}