tauri_updater/
updater.rs

1use std::env;
2use std::fs;
3use std::path::PathBuf;
4
5use crate::http;
6
7use tauri_api::file::{Extract, Move};
8
9pub mod github;
10
11/// Status returned after updating
12///
13/// Wrapped `String`s are version tags
14#[derive(Debug, Clone)]
15pub enum Status {
16  UpToDate(String),
17  Updated(String),
18}
19impl Status {
20  /// Return the version tag
21  pub fn version(&self) -> &str {
22    use Status::*;
23    match *self {
24      UpToDate(ref s) => s,
25      Updated(ref s) => s,
26    }
27  }
28
29  /// Returns `true` if `Status::UpToDate`
30  pub fn uptodate(&self) -> bool {
31    match *self {
32      Status::UpToDate(_) => true,
33      _ => false,
34    }
35  }
36
37  /// Returns `true` if `Status::Updated`
38  pub fn updated(&self) -> bool {
39    match *self {
40      Status::Updated(_) => true,
41      _ => false,
42    }
43  }
44}
45
46#[derive(Clone, Debug)]
47pub struct Release {
48  pub version: String,
49  pub asset_name: String,
50  pub download_url: String,
51}
52
53#[derive(Debug)]
54pub struct UpdateBuilder {
55  release: Option<Release>,
56  bin_name: Option<String>,
57  bin_install_path: Option<PathBuf>,
58  bin_path_in_archive: Option<PathBuf>,
59  show_download_progress: bool,
60  show_output: bool,
61  current_version: Option<String>,
62}
63impl UpdateBuilder {
64  /// Initialize a new builder, defaulting the `bin_install_path` to the current
65  /// executable's path
66  ///
67  /// * Errors:
68  ///     * Io - Determining current exe path
69  pub fn new() -> crate::Result<UpdateBuilder> {
70    Ok(Self {
71      release: None,
72      bin_name: None,
73      bin_install_path: Some(env::current_exe()?),
74      bin_path_in_archive: None,
75      show_download_progress: false,
76      show_output: true,
77      current_version: None,
78    })
79  }
80
81  pub fn release(&mut self, release: Release) -> &mut Self {
82    self.release = Some(release);
83    self
84  }
85
86  /// Set the current app version, used to compare against the latest available version.
87  /// The `cargo_crate_version!` macro can be used to pull the version from your `Cargo.toml`
88  pub fn current_version(&mut self, ver: &str) -> &mut Self {
89    self.current_version = Some(ver.to_owned());
90    self
91  }
92
93  /// Set the exe's name. Also sets `bin_path_in_archive` if it hasn't already been set.
94  pub fn bin_name(&mut self, name: &str) -> &mut Self {
95    self.bin_name = Some(name.to_owned());
96    if self.bin_path_in_archive.is_none() {
97      self.bin_path_in_archive = Some(PathBuf::from(name));
98    }
99    self
100  }
101
102  /// Set the installation path for the new exe, defaults to the current
103  /// executable's path
104  pub fn bin_install_path(&mut self, bin_install_path: &str) -> &mut Self {
105    self.bin_install_path = Some(PathBuf::from(bin_install_path));
106    self
107  }
108
109  /// Set the path of the exe inside the release tarball. This is the location
110  /// of the executable relative to the base of the tar'd directory and is the
111  /// path that will be copied to the `bin_install_path`. If not specified, this
112  /// will default to the value of `bin_name`. This only needs to be specified if
113  /// the path to the binary (from the root of the tarball) is not equal to just
114  /// the `bin_name`.
115  ///
116  /// # Example
117  ///
118  /// For a tarball `myapp.tar.gz` with the contents:
119  ///
120  /// ```shell
121  /// myapp.tar/
122  ///  |------- bin/
123  ///  |         |--- myapp  # <-- executable
124  /// ```
125  ///
126  /// The path provided should be:
127  ///
128  /// ```
129  /// # use tauri_updater::updater::Update;
130  /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
131  /// Update::configure()?
132  ///     .bin_path_in_archive("bin/myapp")
133  /// #   .build()?;
134  /// # Ok(())
135  /// # }
136  /// ```
137  pub fn bin_path_in_archive(&mut self, bin_path: &str) -> &mut Self {
138    self.bin_path_in_archive = Some(PathBuf::from(bin_path));
139    self
140  }
141
142  /// Toggle download progress bar, defaults to `off`.
143  pub fn show_download_progress(&mut self, show: bool) -> &mut Self {
144    self.show_download_progress = show;
145    self
146  }
147
148  /// Toggle update output information, defaults to `true`.
149  pub fn show_output(&mut self, show: bool) -> &mut Self {
150    self.show_output = show;
151    self
152  }
153
154  /// Confirm config and create a ready-to-use `Update`
155  ///
156  /// * Errors:
157  ///     * Config - Invalid `Update` configuration
158  pub fn build(&self) -> crate::Result<Update> {
159    Ok(Update {
160      release: if let Some(ref release) = self.release {
161        release.to_owned()
162      } else {
163        bail!(crate::ErrorKind::Config, "`release` required")
164      },
165      bin_name: if let Some(ref name) = self.bin_name {
166        name.to_owned()
167      } else {
168        bail!(crate::ErrorKind::Config, "`bin_name` required")
169      },
170      bin_install_path: if let Some(ref path) = self.bin_install_path {
171        path.to_owned()
172      } else {
173        bail!(crate::ErrorKind::Config, "`bin_install_path` required")
174      },
175      bin_path_in_archive: if let Some(ref path) = self.bin_path_in_archive {
176        path.to_owned()
177      } else {
178        bail!(crate::ErrorKind::Config, "`bin_path_in_archive` required")
179      },
180      current_version: if let Some(ref ver) = self.current_version {
181        ver.to_owned()
182      } else {
183        bail!(crate::ErrorKind::Config, "`current_version` required")
184      },
185      show_download_progress: self.show_download_progress,
186      show_output: self.show_output,
187    })
188  }
189}
190
191/// Updates to a specified or latest release distributed
192#[derive(Debug)]
193pub struct Update {
194  release: Release,
195  current_version: String,
196  bin_name: String,
197  bin_install_path: PathBuf,
198  bin_path_in_archive: PathBuf,
199  show_download_progress: bool,
200  show_output: bool,
201}
202impl Update {
203  /// Initialize a new `Update` builder
204  pub fn configure() -> crate::Result<UpdateBuilder> {
205    UpdateBuilder::new()
206  }
207
208  fn print_flush(&self, msg: &str) -> crate::Result<()> {
209    if self.show_output {
210      print_flush!("{}", msg);
211    }
212    Ok(())
213  }
214
215  fn println(&self, msg: &str) {
216    if self.show_output {
217      println!("{}", msg);
218    }
219  }
220
221  pub fn update(self) -> crate::Result<Status> {
222    self.println(&format!(
223      "Checking current version... v{}",
224      self.current_version
225    ));
226
227    if self.show_output {
228      println!("\n{} release status:", self.bin_name);
229      println!("  * Current exe: {:?}", self.bin_install_path);
230      println!("  * New exe download url: {:?}", self.release.download_url);
231      println!(
232        "\nThe new release will be downloaded/extracted and the existing binary will be replaced."
233      );
234    }
235
236    let tmp_dir_parent = self
237      .bin_install_path
238      .parent()
239      .ok_or_else(|| crate::ErrorKind::Updater("Failed to determine parent dir".into()))?;
240    let tmp_dir =
241      tempdir::TempDir::new_in(&tmp_dir_parent, &format!("{}_download", self.bin_name))?;
242    let tmp_archive_path = tmp_dir.path().join(&self.release.asset_name);
243    let mut tmp_archive = fs::File::create(&tmp_archive_path)?;
244
245    self.println("Downloading...");
246    http::download(
247      self.release.download_url.clone(),
248      &mut tmp_archive,
249      self.show_download_progress,
250    )?;
251
252    self.print_flush("Extracting archive... ")?;
253    Extract::from_source(&tmp_archive_path)
254      .extract_file(&tmp_dir.path(), &self.bin_path_in_archive)?;
255    let new_exe = tmp_dir.path().join(&self.bin_path_in_archive);
256    self.println("Done");
257
258    self.print_flush("Replacing binary file... ")?;
259    let tmp_file = tmp_dir.path().join(&format!("__{}_backup", self.bin_name));
260    Move::from_source(&new_exe)
261      .replace_using_temp(&tmp_file)
262      .to_dest(&self.bin_install_path)?;
263    self.println("Done");
264    Ok(Status::Updated(self.release.version))
265  }
266}