self_update_extras/
restart.rs1use self_update::Status;
4use self_update::errors::{Error, Result};
5use self_update::update::{Release, ReleaseUpdate, UpdateStatus};
6
7const DEFAULT_GUARD_ENV: &str = "RESTART_GUARD";
9
10#[derive(Default)]
17pub struct UpdateBuilder {
18 release_update: Option<Box<dyn ReleaseUpdate>>,
19 guard_env: Option<String>,
20}
21
22impl UpdateBuilder {
23 pub fn new() -> Self {
25 Default::default()
26 }
27
28 pub fn release_update(&mut self, release_update: Box<dyn ReleaseUpdate>) -> &mut Self {
30 self.release_update = Some(release_update);
31 self
32 }
33
34 pub fn guard_env(&mut self, env: &str) -> &mut Self {
37 self.guard_env = Some(env.to_owned());
38 self
39 }
40
41 pub fn build(&mut self) -> Result<Box<dyn ReleaseUpdate>> {
46 let inner = self
47 .release_update
48 .take()
49 .ok_or_else(|| Error::Config("`release_update` required".to_owned()))?;
50
51 Ok(Box::new(Update {
52 inner,
53 guard_env: self
54 .guard_env
55 .clone()
56 .unwrap_or_else(|| DEFAULT_GUARD_ENV.to_owned()),
57 }))
58 }
59}
60
61pub struct Update {
80 inner: Box<dyn ReleaseUpdate>,
81 guard_env: String,
82}
83
84impl Update {
85 pub fn configure() -> UpdateBuilder {
87 UpdateBuilder::new()
88 }
89
90 fn restart(&self) -> Result<()> {
96 let args: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
97 let mut command = std::process::Command::new(self.inner.bin_install_path());
98 command.args(&args).env(self.guard_env.clone(), "1");
99
100 #[cfg(unix)]
101 {
102 use std::os::unix::process::CommandExt;
103 let err = command.exec();
104 Err(Error::Release(format!(
105 "re-executing updated binary: {}",
106 err
107 )))
108 }
109
110 #[cfg(windows)]
111 {
112 let status = command
113 .status()
114 .map_err(|e| Error::Release(format!("re-executing updated binary: {}", e)))?;
115 std::process::exit(status.code().unwrap_or(0));
116 }
117 }
118}
119
120impl ReleaseUpdate for Update {
121 fn get_latest_release(&self) -> Result<Release> {
122 self.inner.get_latest_release()
123 }
124
125 fn get_latest_releases(&self, current_version: &str) -> Result<Vec<Release>> {
126 self.inner.get_latest_releases(current_version)
127 }
128
129 fn get_release_version(&self, ver: &str) -> Result<Release> {
130 self.inner.get_release_version(ver)
131 }
132
133 fn current_version(&self) -> String {
134 self.inner.current_version()
135 }
136
137 fn target(&self) -> String {
138 self.inner.target()
139 }
140
141 fn target_version(&self) -> Option<String> {
142 self.inner.target_version()
143 }
144
145 fn bin_name(&self) -> String {
146 self.inner.bin_name()
147 }
148
149 fn bin_install_path(&self) -> std::path::PathBuf {
150 self.inner.bin_install_path()
151 }
152
153 fn bin_path_in_archive(&self) -> String {
154 self.inner.bin_path_in_archive()
155 }
156
157 fn show_download_progress(&self) -> bool {
158 self.inner.show_download_progress()
159 }
160
161 fn show_output(&self) -> bool {
162 self.inner.show_output()
163 }
164
165 fn no_confirm(&self) -> bool {
166 self.inner.no_confirm()
167 }
168
169 fn progress_template(&self) -> String {
170 self.inner.progress_template()
171 }
172
173 fn progress_chars(&self) -> String {
174 self.inner.progress_chars()
175 }
176
177 fn auth_token(&self) -> Option<String> {
178 self.inner.auth_token()
179 }
180
181 fn update(&self) -> Result<Status> {
182 if std::env::var_os(&self.guard_env).is_some() {
184 return Ok(Status::UpToDate(self.current_version()));
185 }
186
187 let status = self.inner.update()?;
188 if let Status::Updated(_) = status {
189 self.restart()?;
190 }
191 Ok(status)
192 }
193
194 fn update_extended(&self) -> Result<UpdateStatus> {
195 if std::env::var_os(&self.guard_env).is_some() {
197 return Ok(UpdateStatus::UpToDate);
198 }
199
200 let status = self.inner.update_extended()?;
201 if let UpdateStatus::Updated(_) = status {
202 self.restart()?;
203 }
204 Ok(status)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::test_support::MockRelease;
212
213 #[test]
218 #[serial_test::serial(env_guard)]
219 fn update_is_skipped_when_guard_env_is_set() {
220 let guard = "AUTO_UPDATE_TEST_GUARD_SET";
221 unsafe { std::env::set_var(guard, "1") };
222
223 let mock = MockRelease::new("mock-guard-set").report_updated(true);
224 let calls = mock.call_counter();
225 let updater = Update::configure()
226 .release_update(Box::new(mock))
227 .guard_env(guard)
228 .build()
229 .unwrap();
230
231 let status = updater.update().unwrap();
232
233 assert!(matches!(status, Status::UpToDate(_)));
234 assert_eq!(
235 calls.get(),
236 0,
237 "inner update must be skipped when the guard is set"
238 );
239
240 unsafe { std::env::remove_var(guard) };
241 }
242
243 #[test]
244 #[serial_test::serial(env_guard)]
245 fn update_extended_is_skipped_when_guard_env_is_set() {
246 let guard = "AUTO_UPDATE_TEST_GUARD_EXT";
247 unsafe { std::env::set_var(guard, "1") };
248
249 let mock = MockRelease::new("mock-guard-ext");
250 let calls = mock.call_counter();
251 let updater = Update::configure()
252 .release_update(Box::new(mock))
253 .guard_env(guard)
254 .build()
255 .unwrap();
256
257 let status = updater.update_extended().unwrap();
258
259 assert!(matches!(status, UpdateStatus::UpToDate));
260 assert_eq!(calls.get(), 0);
261
262 unsafe { std::env::remove_var(guard) };
263 }
264
265 #[test]
266 #[serial_test::serial(env_guard)]
267 fn update_runs_inner_when_guard_env_is_unset() {
268 let guard = "AUTO_UPDATE_TEST_GUARD_UNSET";
269 unsafe { std::env::remove_var(guard) };
270
271 let mock = MockRelease::new("mock-guard-unset");
273 let calls = mock.call_counter();
274 let updater = Update::configure()
275 .release_update(Box::new(mock))
276 .guard_env(guard)
277 .build()
278 .unwrap();
279
280 let status = updater.update().unwrap();
281
282 assert!(matches!(status, Status::UpToDate(_)));
283 assert_eq!(calls.get(), 1);
284 }
285
286 #[test]
287 fn build_requires_a_release_update() {
288 assert!(Update::configure().build().is_err());
289 }
290
291 #[test]
292 fn metadata_methods_delegate_to_inner() {
293 let updater = Update::configure()
294 .release_update(Box::new(MockRelease::new("mock-forward")))
295 .build()
296 .unwrap();
297
298 assert_eq!(updater.current_version(), "1.0.0");
299 assert_eq!(updater.target(), "test-target");
300 assert_eq!(updater.target_version(), None);
301 assert_eq!(updater.bin_name(), "mock-forward");
302 assert_eq!(
303 updater.bin_install_path(),
304 std::env::temp_dir().join("mock-forward")
305 );
306 assert_eq!(updater.bin_path_in_archive(), "mock-forward");
307 assert!(!updater.show_download_progress());
308 assert!(!updater.show_output());
309 assert!(updater.no_confirm());
310 assert_eq!(updater.progress_template(), "");
311 assert_eq!(updater.progress_chars(), "");
312 assert_eq!(updater.auth_token(), None);
313 assert!(updater.get_latest_release().is_ok());
314 assert!(updater.get_latest_releases("1.0.0").is_ok());
315 assert!(updater.get_release_version("1.0.0").is_ok());
316 }
317}