1use std::{
2 os::windows::process::CommandExt,
3 path::Path,
4 time::{Duration, Instant},
5};
6
7use anyhow::{Context as _, Result};
8use windows::Win32::{
9 Foundation::{HWND, LPARAM, WPARAM},
10 System::Threading::CREATE_NEW_PROCESS_GROUP,
11 UI::WindowsAndMessaging::PostMessageW,
12};
13
14use crate::windows_impl::WM_JOB_UPDATED;
15
16type Job = fn(&Path) -> Result<()>;
17
18#[cfg(not(test))]
19pub(crate) const JOBS: [Job; 6] = [
20 // Delete old files
21 |app_dir| {
22 let zed_executable = app_dir.join("Zed.exe");
23 log::info!("Removing old file: {}", zed_executable.display());
24 std::fs::remove_file(&zed_executable).context(format!(
25 "Failed to remove old file {}",
26 zed_executable.display()
27 ))
28 },
29 |app_dir| {
30 let zed_cli = app_dir.join("bin\\zed.exe");
31 log::info!("Removing old file: {}", zed_cli.display());
32 std::fs::remove_file(&zed_cli)
33 .context(format!("Failed to remove old file {}", zed_cli.display()))
34 },
35 // Copy new files
36 |app_dir| {
37 let zed_executable_source = app_dir.join("install\\Zed.exe");
38 let zed_executable_dest = app_dir.join("Zed.exe");
39 log::info!(
40 "Copying new file {} to {}",
41 zed_executable_source.display(),
42 zed_executable_dest.display()
43 );
44 std::fs::copy(&zed_executable_source, &zed_executable_dest)
45 .map(|_| ())
46 .context(format!(
47 "Failed to copy new file {} to {}",
48 zed_executable_source.display(),
49 zed_executable_dest.display()
50 ))
51 },
52 |app_dir| {
53 let zed_cli_source = app_dir.join("install\\bin\\zed.exe");
54 let zed_cli_dest = app_dir.join("bin\\zed.exe");
55 log::info!(
56 "Copying new file {} to {}",
57 zed_cli_source.display(),
58 zed_cli_dest.display()
59 );
60 std::fs::copy(&zed_cli_source, &zed_cli_dest)
61 .map(|_| ())
62 .context(format!(
63 "Failed to copy new file {} to {}",
64 zed_cli_source.display(),
65 zed_cli_dest.display()
66 ))
67 },
68 // Clean up installer folder and updates folder
69 |app_dir| {
70 let updates_folder = app_dir.join("updates");
71 log::info!("Cleaning up: {}", updates_folder.display());
72 std::fs::remove_dir_all(&updates_folder).context(format!(
73 "Failed to remove updates folder {}",
74 updates_folder.display()
75 ))
76 },
77 |app_dir| {
78 let installer_folder = app_dir.join("install");
79 log::info!("Cleaning up: {}", installer_folder.display());
80 std::fs::remove_dir_all(&installer_folder).context(format!(
81 "Failed to remove installer folder {}",
82 installer_folder.display()
83 ))
84 },
85];
86
87#[cfg(test)]
88pub(crate) const JOBS: [Job; 2] = [
89 |_| {
90 std::thread::sleep(Duration::from_millis(1000));
91 if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
92 match config.as_str() {
93 "err" => Err(std::io::Error::new(
94 std::io::ErrorKind::Other,
95 "Simulated error",
96 ))
97 .context("Anyhow!"),
98 _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
99 }
100 } else {
101 Ok(())
102 }
103 },
104 |_| {
105 std::thread::sleep(Duration::from_millis(1000));
106 if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
107 match config.as_str() {
108 "err" => Err(std::io::Error::new(
109 std::io::ErrorKind::Other,
110 "Simulated error",
111 ))
112 .context("Anyhow!"),
113 _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
114 }
115 } else {
116 Ok(())
117 }
118 },
119];
120
121pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool) -> Result<()> {
122 let hwnd = hwnd.map(|ptr| HWND(ptr as _));
123
124 for job in JOBS.iter() {
125 let start = Instant::now();
126 loop {
127 anyhow::ensure!(start.elapsed().as_secs() <= 2, "Timed out");
128 match (*job)(app_dir) {
129 Ok(_) => {
130 unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
131 break;
132 }
133 Err(err) => {
134 // Check if it's a "not found" error
135 let io_err = err.downcast_ref::<std::io::Error>().unwrap();
136 if io_err.kind() == std::io::ErrorKind::NotFound {
137 log::warn!("File or folder not found.");
138 unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
139 break;
140 }
141
142 log::error!("Operation failed: {}", err);
143 std::thread::sleep(Duration::from_millis(50));
144 }
145 }
146 }
147 }
148 if launch {
149 let _ = std::process::Command::new(app_dir.join("Zed.exe"))
150 .creation_flags(CREATE_NEW_PROCESS_GROUP.0)
151 .spawn();
152 }
153 log::info!("Update completed successfully");
154 Ok(())
155}
156
157#[cfg(test)]
158mod test {
159 use super::perform_update;
160
161 #[test]
162 fn test_perform_update() {
163 let app_dir = std::path::Path::new("C:/");
164 assert!(perform_update(app_dir, None, false).is_ok());
165
166 // Simulate a timeout
167 unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err") };
168 let ret = perform_update(app_dir, None, false);
169 assert!(ret.is_err_and(|e| e.to_string().as_str() == "Timed out"));
170 }
171}