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] = &[
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 |app_dir| {
36 let zed_wsl = app_dir.join("bin\\zed");
37 log::info!("Removing old file: {}", zed_wsl.display());
38 std::fs::remove_file(&zed_wsl)
39 .context(format!("Failed to remove old file {}", zed_wsl.display()))
40 },
41 |app_dir| {
42 let open_console = app_dir.join("OpenConsole.exe");
43 log::info!("Removing old file: {}", open_console.display());
44 std::fs::remove_file(&open_console).context(format!(
45 "Failed to remove old file {}",
46 open_console.display()
47 ))
48 },
49 |app_dir| {
50 let conpty = app_dir.join("conpty.dll");
51 log::info!("Removing old file: {}", conpty.display());
52 std::fs::remove_file(&conpty)
53 .context(format!("Failed to remove old file {}", conpty.display()))
54 },
55 // Copy new files
56 |app_dir| {
57 let zed_executable_source = app_dir.join("install\\Zed.exe");
58 let zed_executable_dest = app_dir.join("Zed.exe");
59 log::info!(
60 "Copying new file {} to {}",
61 zed_executable_source.display(),
62 zed_executable_dest.display()
63 );
64 std::fs::copy(&zed_executable_source, &zed_executable_dest)
65 .map(|_| ())
66 .context(format!(
67 "Failed to copy new file {} to {}",
68 zed_executable_source.display(),
69 zed_executable_dest.display()
70 ))
71 },
72 |app_dir| {
73 let zed_cli_source = app_dir.join("install\\bin\\zed.exe");
74 let zed_cli_dest = app_dir.join("bin\\zed.exe");
75 log::info!(
76 "Copying new file {} to {}",
77 zed_cli_source.display(),
78 zed_cli_dest.display()
79 );
80 std::fs::copy(&zed_cli_source, &zed_cli_dest)
81 .map(|_| ())
82 .context(format!(
83 "Failed to copy new file {} to {}",
84 zed_cli_source.display(),
85 zed_cli_dest.display()
86 ))
87 },
88 |app_dir| {
89 let zed_wsl_source = app_dir.join("install\\bin\\zed");
90 let zed_wsl_dest = app_dir.join("bin\\zed");
91 log::info!(
92 "Copying new file {} to {}",
93 zed_wsl_source.display(),
94 zed_wsl_dest.display()
95 );
96 std::fs::copy(&zed_wsl_source, &zed_wsl_dest)
97 .map(|_| ())
98 .context(format!(
99 "Failed to copy new file {} to {}",
100 zed_wsl_source.display(),
101 zed_wsl_dest.display()
102 ))
103 },
104 |app_dir| {
105 let open_console_source = app_dir.join("install\\OpenConsole.exe");
106 let open_console_dest = app_dir.join("OpenConsole.exe");
107 log::info!(
108 "Copying new file {} to {}",
109 open_console_source.display(),
110 open_console_dest.display()
111 );
112 std::fs::copy(&open_console_source, &open_console_dest)
113 .map(|_| ())
114 .context(format!(
115 "Failed to copy new file {} to {}",
116 open_console_source.display(),
117 open_console_dest.display()
118 ))
119 },
120 |app_dir| {
121 let conpty_source = app_dir.join("install\\conpty.dll");
122 let conpty_dest = app_dir.join("conpty.dll");
123 log::info!(
124 "Copying new file {} to {}",
125 conpty_source.display(),
126 conpty_dest.display()
127 );
128 std::fs::copy(&conpty_source, &conpty_dest)
129 .map(|_| ())
130 .context(format!(
131 "Failed to copy new file {} to {}",
132 conpty_source.display(),
133 conpty_dest.display()
134 ))
135 },
136 // Clean up installer folder and updates folder
137 |app_dir| {
138 let updates_folder = app_dir.join("updates");
139 log::info!("Cleaning up: {}", updates_folder.display());
140 std::fs::remove_dir_all(&updates_folder).context(format!(
141 "Failed to remove updates folder {}",
142 updates_folder.display()
143 ))
144 },
145 |app_dir| {
146 let installer_folder = app_dir.join("install");
147 log::info!("Cleaning up: {}", installer_folder.display());
148 std::fs::remove_dir_all(&installer_folder).context(format!(
149 "Failed to remove installer folder {}",
150 installer_folder.display()
151 ))
152 },
153];
154
155#[cfg(test)]
156pub(crate) const JOBS: &[Job] = &[
157 |_| {
158 std::thread::sleep(Duration::from_millis(1000));
159 if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
160 match config.as_str() {
161 "err" => Err(std::io::Error::other("Simulated error")).context("Anyhow!"),
162 _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
163 }
164 } else {
165 Ok(())
166 }
167 },
168 |_| {
169 std::thread::sleep(Duration::from_millis(1000));
170 if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
171 match config.as_str() {
172 "err" => Err(std::io::Error::other("Simulated error")).context("Anyhow!"),
173 _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
174 }
175 } else {
176 Ok(())
177 }
178 },
179];
180
181pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool) -> Result<()> {
182 let hwnd = hwnd.map(|ptr| HWND(ptr as _));
183
184 for job in JOBS.iter() {
185 let start = Instant::now();
186 loop {
187 anyhow::ensure!(start.elapsed().as_secs() <= 2, "Timed out");
188 match (*job)(app_dir) {
189 Ok(_) => {
190 unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
191 break;
192 }
193 Err(err) => {
194 // Check if it's a "not found" error
195 let io_err = err.downcast_ref::<std::io::Error>().unwrap();
196 if io_err.kind() == std::io::ErrorKind::NotFound {
197 log::warn!("File or folder not found.");
198 unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
199 break;
200 }
201
202 log::error!("Operation failed: {}", err);
203 std::thread::sleep(Duration::from_millis(50));
204 }
205 }
206 }
207 }
208 if launch {
209 #[allow(clippy::disallowed_methods, reason = "doesn't run in the main binary")]
210 let _ = std::process::Command::new(app_dir.join("Zed.exe"))
211 .creation_flags(CREATE_NEW_PROCESS_GROUP.0)
212 .spawn();
213 }
214 log::info!("Update completed successfully");
215 Ok(())
216}
217
218#[cfg(test)]
219mod test {
220 use super::perform_update;
221
222 #[test]
223 fn test_perform_update() {
224 let app_dir = std::path::Path::new("C:/");
225 assert!(perform_update(app_dir, None, false).is_ok());
226
227 // Simulate a timeout
228 unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err") };
229 let ret = perform_update(app_dir, None, false);
230 assert!(ret.is_err_and(|e| e.to_string().as_str() == "Timed out"));
231 }
232}