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#[derive(Debug, Clone)]
15pub enum Status {
16 UpToDate(String),
17 Updated(String),
18}
19impl Status {
20 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 pub fn uptodate(&self) -> bool {
31 match *self {
32 Status::UpToDate(_) => true,
33 _ => false,
34 }
35 }
36
37 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 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 pub fn current_version(&mut self, ver: &str) -> &mut Self {
89 self.current_version = Some(ver.to_owned());
90 self
91 }
92
93 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 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 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 pub fn show_download_progress(&mut self, show: bool) -> &mut Self {
144 self.show_download_progress = show;
145 self
146 }
147
148 pub fn show_output(&mut self, show: bool) -> &mut Self {
150 self.show_output = show;
151 self
152 }
153
154 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#[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 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}