1use std::{
2 path::Path,
3 time::{Duration, Instant},
4};
5
6use anyhow::{Context as _, Result};
7use windows::Win32::{
8 Foundation::{HWND, LPARAM, WPARAM},
9 UI::WindowsAndMessaging::PostMessageW,
10};
11
12use crate::windows_impl::WM_JOB_UPDATED;
13
14type Job = fn(&Path) -> Result<()>;
15
16#[cfg(not(test))]
17pub(crate) const JOBS: &[Job] = &[
18 // Delete old files
19 |app_dir| {
20 let zed_executable = app_dir.join("Zed.exe");
21 log::info!("Removing old file: {}", zed_executable.display());
22 std::fs::remove_file(&zed_executable).context(format!(
23 "Failed to remove old file {}",
24 zed_executable.display()
25 ))
26 },
27 |app_dir| {
28 let zed_cli = app_dir.join("bin\\zed.exe");
29 log::info!("Removing old file: {}", zed_cli.display());
30 std::fs::remove_file(&zed_cli)
31 .context(format!("Failed to remove old file {}", zed_cli.display()))
32 },
33 |app_dir| {
34 let zed_wsl = app_dir.join("bin\\zed");
35 log::info!("Removing old file: {}", zed_wsl.display());
36 std::fs::remove_file(&zed_wsl)
37 .context(format!("Failed to remove old file {}", zed_wsl.display()))
38 },
39 // TODO: remove after a few weeks once everyone is on the new version and this file never exists
40 |app_dir| {
41 let open_console = app_dir.join("OpenConsole.exe");
42 if open_console.exists() {
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 Ok(())
50 },
51 |app_dir| {
52 let archs = ["x64", "arm64"];
53 for arch in archs {
54 let open_console = app_dir.join(format!("{arch}\\OpenConsole.exe"));
55 if open_console.exists() {
56 log::info!("Removing old file: {}", open_console.display());
57 std::fs::remove_file(&open_console).context(format!(
58 "Failed to remove old file {}",
59 open_console.display()
60 ))?
61 }
62 }
63 Ok(())
64 },
65 |app_dir| {
66 let conpty = app_dir.join("conpty.dll");
67 log::info!("Removing old file: {}", conpty.display());
68 std::fs::remove_file(&conpty)
69 .context(format!("Failed to remove old file {}", conpty.display()))
70 },
71 // Copy new files
72 |app_dir| {
73 let zed_executable_source = app_dir.join("install\\Zed.exe");
74 let zed_executable_dest = app_dir.join("Zed.exe");
75 log::info!(
76 "Copying new file {} to {}",
77 zed_executable_source.display(),
78 zed_executable_dest.display()
79 );
80 std::fs::copy(&zed_executable_source, &zed_executable_dest)
81 .map(|_| ())
82 .context(format!(
83 "Failed to copy new file {} to {}",
84 zed_executable_source.display(),
85 zed_executable_dest.display()
86 ))
87 },
88 |app_dir| {
89 let zed_cli_source = app_dir.join("install\\bin\\zed.exe");
90 let zed_cli_dest = app_dir.join("bin\\zed.exe");
91 log::info!(
92 "Copying new file {} to {}",
93 zed_cli_source.display(),
94 zed_cli_dest.display()
95 );
96 std::fs::copy(&zed_cli_source, &zed_cli_dest)
97 .map(|_| ())
98 .context(format!(
99 "Failed to copy new file {} to {}",
100 zed_cli_source.display(),
101 zed_cli_dest.display()
102 ))
103 },
104 |app_dir| {
105 let zed_wsl_source = app_dir.join("install\\bin\\zed");
106 let zed_wsl_dest = app_dir.join("bin\\zed");
107 log::info!(
108 "Copying new file {} to {}",
109 zed_wsl_source.display(),
110 zed_wsl_dest.display()
111 );
112 std::fs::copy(&zed_wsl_source, &zed_wsl_dest)
113 .map(|_| ())
114 .context(format!(
115 "Failed to copy new file {} to {}",
116 zed_wsl_source.display(),
117 zed_wsl_dest.display()
118 ))
119 },
120 |app_dir| {
121 let archs = ["x64", "arm64"];
122 for arch in archs {
123 let open_console_source = app_dir.join(format!("install\\{arch}\\OpenConsole.exe"));
124 let open_console_dest = app_dir.join(format!("{arch}\\OpenConsole.exe"));
125 if open_console_source.exists() {
126 log::info!(
127 "Copying new file {} to {}",
128 open_console_source.display(),
129 open_console_dest.display()
130 );
131 let parent = open_console_dest.parent().context(format!(
132 "Failed to get parent directory of {}",
133 open_console_dest.display()
134 ))?;
135 std::fs::create_dir_all(parent)
136 .context(format!("Failed to create directory {}", parent.display()))?;
137 std::fs::copy(&open_console_source, &open_console_dest)
138 .map(|_| ())
139 .context(format!(
140 "Failed to copy new file {} to {}",
141 open_console_source.display(),
142 open_console_dest.display()
143 ))?
144 }
145 }
146 Ok(())
147 },
148 |app_dir| {
149 let conpty_source = app_dir.join("install\\conpty.dll");
150 let conpty_dest = app_dir.join("conpty.dll");
151 log::info!(
152 "Copying new file {} to {}",
153 conpty_source.display(),
154 conpty_dest.display()
155 );
156 std::fs::copy(&conpty_source, &conpty_dest)
157 .map(|_| ())
158 .context(format!(
159 "Failed to copy new file {} to {}",
160 conpty_source.display(),
161 conpty_dest.display()
162 ))
163 },
164 // Clean up installer folder and updates folder
165 |app_dir| {
166 let updates_folder = app_dir.join("updates");
167 log::info!("Cleaning up: {}", updates_folder.display());
168 std::fs::remove_dir_all(&updates_folder).context(format!(
169 "Failed to remove updates folder {}",
170 updates_folder.display()
171 ))
172 },
173 |app_dir| {
174 let installer_folder = app_dir.join("install");
175 log::info!("Cleaning up: {}", installer_folder.display());
176 std::fs::remove_dir_all(&installer_folder).context(format!(
177 "Failed to remove installer folder {}",
178 installer_folder.display()
179 ))
180 },
181];
182
183#[cfg(test)]
184pub(crate) const JOBS: &[Job] = &[
185 |_| {
186 std::thread::sleep(Duration::from_millis(1000));
187 if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
188 match config.as_str() {
189 "err" => Err(std::io::Error::other("Simulated error")).context("Anyhow!"),
190 _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
191 }
192 } else {
193 Ok(())
194 }
195 },
196 |_| {
197 std::thread::sleep(Duration::from_millis(1000));
198 if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
199 match config.as_str() {
200 "err" => Err(std::io::Error::other("Simulated error")).context("Anyhow!"),
201 _ => panic!("Unknown ZED_AUTO_UPDATE value: {}", config),
202 }
203 } else {
204 Ok(())
205 }
206 },
207];
208
209pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool) -> Result<()> {
210 let hwnd = hwnd.map(|ptr| HWND(ptr as _));
211
212 for job in JOBS.iter() {
213 let start = Instant::now();
214 loop {
215 anyhow::ensure!(start.elapsed().as_secs() <= 2, "Timed out");
216 match (*job)(app_dir) {
217 Ok(_) => {
218 unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
219 break;
220 }
221 Err(err) => {
222 // Check if it's a "not found" error
223 let io_err = err.downcast_ref::<std::io::Error>().unwrap();
224 if io_err.kind() == std::io::ErrorKind::NotFound {
225 log::warn!("File or folder not found.");
226 unsafe { PostMessageW(hwnd, WM_JOB_UPDATED, WPARAM(0), LPARAM(0))? };
227 break;
228 }
229
230 log::error!("Operation failed: {} ({:?})", err, io_err.kind());
231 std::thread::sleep(Duration::from_millis(50));
232 }
233 }
234 }
235 }
236 if launch {
237 #[allow(clippy::disallowed_methods, reason = "doesn't run in the main binary")]
238 let _ = std::process::Command::new(app_dir.join("Zed.exe")).spawn();
239 }
240 log::info!("Update completed successfully");
241 Ok(())
242}
243
244#[cfg(test)]
245mod test {
246 use super::perform_update;
247
248 #[test]
249 fn test_perform_update() {
250 let app_dir = std::path::Path::new("C:/");
251 assert!(perform_update(app_dir, None, false).is_ok());
252
253 // Simulate a timeout
254 unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err") };
255 let ret = perform_update(app_dir, None, false);
256 assert!(ret.is_err_and(|e| e.to_string().as_str() == "Timed out"));
257 }
258}