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 pub fn guard_env(&self) -> &str {
92 &self.guard_env
93 }
94
95 fn restart(&self) -> Result<()> {
101 let args: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
102 let mut command = std::process::Command::new(self.inner.bin_install_path());
103 command.args(&args).env(self.guard_env.clone(), "1");
104
105 #[cfg(unix)]
106 {
107 use std::os::unix::process::CommandExt;
108 let err = command.exec();
109 Err(Error::Release(format!(
110 "re-executing updated binary: {}",
111 err
112 )))
113 }
114
115 #[cfg(windows)]
116 {
117 let status = command
118 .status()
119 .map_err(|e| Error::Release(format!("re-executing updated binary: {}", e)))?;
120 std::process::exit(status.code().unwrap_or(0));
121 }
122 }
123}
124
125impl ReleaseUpdate for Update {
126 fn get_latest_release(&self) -> Result<Release> {
127 self.inner.get_latest_release()
128 }
129
130 fn get_latest_releases(&self, current_version: &str) -> Result<Vec<Release>> {
131 self.inner.get_latest_releases(current_version)
132 }
133
134 fn get_release_version(&self, ver: &str) -> Result<Release> {
135 self.inner.get_release_version(ver)
136 }
137
138 fn current_version(&self) -> String {
139 self.inner.current_version()
140 }
141
142 fn target(&self) -> String {
143 self.inner.target()
144 }
145
146 fn target_version(&self) -> Option<String> {
147 self.inner.target_version()
148 }
149
150 fn bin_name(&self) -> String {
151 self.inner.bin_name()
152 }
153
154 fn bin_install_path(&self) -> std::path::PathBuf {
155 self.inner.bin_install_path()
156 }
157
158 fn bin_path_in_archive(&self) -> String {
159 self.inner.bin_path_in_archive()
160 }
161
162 fn show_download_progress(&self) -> bool {
163 self.inner.show_download_progress()
164 }
165
166 fn show_output(&self) -> bool {
167 self.inner.show_output()
168 }
169
170 fn no_confirm(&self) -> bool {
171 self.inner.no_confirm()
172 }
173
174 fn progress_template(&self) -> String {
175 self.inner.progress_template()
176 }
177
178 fn progress_chars(&self) -> String {
179 self.inner.progress_chars()
180 }
181
182 fn auth_token(&self) -> Option<String> {
183 self.inner.auth_token()
184 }
185
186 fn update(&self) -> Result<Status> {
187 let current_version = self.current_version();
188 self.update_extended()
189 .map(|s| s.into_status(current_version))
190 }
191
192 fn update_extended(&self) -> Result<UpdateStatus> {
193 if std::env::var_os(&self.guard_env).is_some() {
195 return Ok(UpdateStatus::UpToDate);
196 }
197
198 let status = self.inner.update_extended()?;
199 if let UpdateStatus::Updated(_) = status {
200 self.restart()?;
201 }
202 Ok(status)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::test_support::MockRelease;
210
211 #[test]
216 #[serial_test::serial(env_guard)]
217 fn update_is_skipped_when_guard_env_is_set() {
218 let guard = "AUTO_UPDATE_TEST_GUARD_SET";
219 unsafe { std::env::set_var(guard, "1") };
220
221 let mock = MockRelease::new("mock-guard-set").report_updated(true);
222 let calls = mock.call_counter();
223 let updater = Update::configure()
224 .release_update(Box::new(mock))
225 .guard_env(guard)
226 .build()
227 .unwrap();
228
229 let status = updater.update().unwrap();
230
231 assert!(matches!(status, Status::UpToDate(_)));
232 assert_eq!(
233 calls.get(),
234 0,
235 "inner update must be skipped when the guard is set"
236 );
237
238 unsafe { std::env::remove_var(guard) };
239 }
240
241 #[test]
242 #[serial_test::serial(env_guard)]
243 fn update_extended_is_skipped_when_guard_env_is_set() {
244 let guard = "AUTO_UPDATE_TEST_GUARD_EXT";
245 unsafe { std::env::set_var(guard, "1") };
246
247 let mock = MockRelease::new("mock-guard-ext");
248 let calls = mock.call_counter();
249 let updater = Update::configure()
250 .release_update(Box::new(mock))
251 .guard_env(guard)
252 .build()
253 .unwrap();
254
255 let status = updater.update_extended().unwrap();
256
257 assert!(matches!(status, UpdateStatus::UpToDate));
258 assert_eq!(calls.get(), 0);
259
260 unsafe { std::env::remove_var(guard) };
261 }
262
263 #[test]
264 #[serial_test::serial(env_guard)]
265 fn update_runs_inner_when_guard_env_is_unset() {
266 let guard = "AUTO_UPDATE_TEST_GUARD_UNSET";
267 unsafe { std::env::remove_var(guard) };
268
269 let mock = MockRelease::new("mock-guard-unset");
271 let calls = mock.call_counter();
272 let updater = Update::configure()
273 .release_update(Box::new(mock))
274 .guard_env(guard)
275 .build()
276 .unwrap();
277
278 let status = updater.update().unwrap();
279
280 assert!(matches!(status, Status::UpToDate(_)));
281 assert_eq!(calls.get(), 1);
282 }
283
284 #[test]
285 fn guard_env_getter_reports_the_configured_guard() {
286 let updater = Update {
287 inner: Box::new(MockRelease::new("mock-guard-getter")),
288 guard_env: "CUSTOM_GUARD".to_owned(),
289 };
290 assert_eq!(updater.guard_env(), "CUSTOM_GUARD");
291 }
292
293 #[test]
294 fn build_requires_a_release_update() {
295 assert!(Update::configure().build().is_err());
296 }
297
298 #[test]
299 fn metadata_methods_delegate_to_inner() {
300 let updater = Update::configure()
301 .release_update(Box::new(MockRelease::new("mock-forward")))
302 .build()
303 .unwrap();
304
305 assert_eq!(updater.current_version(), "1.0.0");
306 assert_eq!(updater.target(), "test-target");
307 assert_eq!(updater.target_version(), None);
308 assert_eq!(updater.bin_name(), "mock-forward");
309 assert_eq!(
310 updater.bin_install_path(),
311 std::env::temp_dir().join("mock-forward")
312 );
313 assert_eq!(updater.bin_path_in_archive(), "mock-forward");
314 assert!(!updater.show_download_progress());
315 assert!(!updater.show_output());
316 assert!(updater.no_confirm());
317 assert_eq!(updater.progress_template(), "");
318 assert_eq!(updater.progress_chars(), "");
319 assert_eq!(updater.auth_token(), None);
320 assert!(updater.get_latest_release().is_ok());
321 assert!(updater.get_latest_releases("1.0.0").is_ok());
322 assert!(updater.get_release_version("1.0.0").is_ok());
323 }
324}