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