Skip to content

Commit f83b9e9

Browse files
enhance(updater): use named tempfile on Windows (#1544)
* Use named tempfile on Windows * append installer * Add change file * Fix ci * Wrap in a folder * Name temp dir for eaiser debugging * format * temp_dir * target_os * Document use updater_builder instead
1 parent 77ee644 commit f83b9e9

File tree

5 files changed

+71
-27
lines changed

5 files changed

+71
-27
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"updater": patch
3+
---
4+
5+
On Windows, use a named tempfile with `<app name>-<version>-installer.exe` (or `.msi`) for v2 updater
6+
7+
**Breaking Change**: `UpdaterBuilder::new` now takes one more argument `app_name: String`

plugins/updater/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ pub trait UpdaterExt<R: Runtime> {
7070
impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
7171
fn updater_builder(&self) -> UpdaterBuilder {
7272
let app = self.app_handle();
73-
let version = app.package_info().version.clone();
73+
let package_info = app.package_info();
7474
let UpdaterState { config, target } = self.state::<UpdaterState>().inner();
7575

76-
let mut builder = UpdaterBuilder::new(version, config.clone());
76+
let mut builder = UpdaterBuilder::new(
77+
package_info.name.clone(),
78+
package_info.version.clone(),
79+
config.clone(),
80+
);
7781

7882
if let Some(target) = target {
7983
builder = builder.target(target);

plugins/updater/src/updater.rs

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ impl RemoteRelease {
9494
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
9595

9696
pub struct UpdaterBuilder {
97+
app_name: String,
9798
current_version: Version,
9899
config: Config,
99100
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
@@ -109,14 +110,17 @@ pub struct UpdaterBuilder {
109110
}
110111

111112
impl UpdaterBuilder {
112-
pub fn new(current_version: Version, config: crate::Config) -> Self {
113+
/// It's prefered to use [`crate::UpdaterExt::updater_builder`] instead of
114+
/// constructing a [`UpdaterBuilder`] with this function yourself
115+
pub fn new(app_name: String, current_version: Version, config: crate::Config) -> Self {
113116
Self {
114117
installer_args: config
115118
.windows
116119
.as_ref()
117120
.map(|w| w.installer_args.clone())
118121
.unwrap_or_default(),
119122
current_exe_args: Vec::new(),
123+
app_name,
120124
current_version,
121125
config,
122126
version_comparator: None,
@@ -239,6 +243,7 @@ impl UpdaterBuilder {
239243

240244
Ok(Updater {
241245
config: self.config,
246+
app_name: self.app_name,
242247
current_version: self.current_version,
243248
version_comparator: self.version_comparator,
244249
timeout: self.timeout,
@@ -270,6 +275,7 @@ impl UpdaterBuilder {
270275

271276
pub struct Updater {
272277
config: Config,
278+
app_name: String,
273279
current_version: Version,
274280
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
275281
timeout: Option<Duration>,
@@ -386,6 +392,7 @@ impl Updater {
386392
Some(Update {
387393
config: self.config.clone(),
388394
on_before_exit: self.on_before_exit.clone(),
395+
app_name: self.app_name.clone(),
389396
current_version: self.current_version.to_string(),
390397
target: self.target.clone(),
391398
extract_path: self.extract_path.clone(),
@@ -436,6 +443,9 @@ pub struct Update {
436443
/// Extract path
437444
#[allow(unused)]
438445
extract_path: PathBuf,
446+
/// App name, used for creating named tempfiles on Windows
447+
#[allow(unused)]
448+
app_name: String,
439449
#[allow(unused)]
440450
installer_args: Vec<OsString>,
441451
#[allow(unused)]
@@ -584,7 +594,7 @@ impl Update {
584594
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
585595
};
586596

587-
let updater_type = Self::extract(bytes)?;
597+
let updater_type = self.extract(bytes)?;
588598

589599
let install_mode = self.config.install_mode();
590600
let current_args = &self.current_exe_args()[1..];
@@ -663,24 +673,31 @@ impl Update {
663673
.collect::<Vec<_>>()
664674
}
665675

666-
fn extract(bytes: &[u8]) -> Result<WindowsUpdaterType> {
676+
fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
667677
#[cfg(feature = "zip")]
668678
if infer::archive::is_zip(bytes) {
669-
return Self::extract_zip(bytes);
679+
return self.extract_zip(bytes);
670680
}
671681

672-
Self::extract_exe(bytes)
682+
self.extract_exe(bytes)
683+
}
684+
685+
fn make_temp_dir(&self) -> Result<PathBuf> {
686+
Ok(tempfile::Builder::new()
687+
.prefix(&format!("{}-{}-updater-", self.app_name, self.version))
688+
.tempdir()?
689+
.into_path())
673690
}
674691

675692
#[cfg(feature = "zip")]
676-
fn extract_zip(bytes: &[u8]) -> Result<WindowsUpdaterType> {
677-
let tmp_dir = tempfile::Builder::new().tempdir()?.into_path();
693+
fn extract_zip(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
694+
let temp_dir = self.make_temp_dir()?;
678695

679696
let archive = Cursor::new(bytes);
680697
let mut extractor = zip::ZipArchive::new(archive)?;
681-
extractor.extract(&tmp_dir)?;
698+
extractor.extract(&temp_dir)?;
682699

683-
let paths = std::fs::read_dir(&tmp_dir)?;
700+
let paths = std::fs::read_dir(&temp_dir)?;
684701
for path in paths {
685702
let path = path?.path();
686703
let ext = path.extension();
@@ -694,22 +711,31 @@ impl Update {
694711
Err(crate::Error::BinaryNotFoundInArchive)
695712
}
696713

697-
fn extract_exe(bytes: &[u8]) -> Result<WindowsUpdaterType> {
714+
fn extract_exe(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
698715
if infer::app::is_exe(bytes) {
699-
let (path, temp) = Self::write_to_temp(bytes, ".exe")?;
716+
let (path, temp) = self.write_to_temp(bytes, ".exe")?;
700717
Ok(WindowsUpdaterType::nsis(path, temp))
701718
} else if infer::archive::is_msi(bytes) {
702-
let (path, temp) = Self::write_to_temp(bytes, ".msi")?;
719+
let (path, temp) = self.write_to_temp(bytes, ".msi")?;
703720
Ok(WindowsUpdaterType::msi(path, temp))
704721
} else {
705722
Err(crate::Error::InvalidUpdaterFormat)
706723
}
707724
}
708725

709-
fn write_to_temp(bytes: &[u8], ext: &str) -> Result<(PathBuf, Option<tempfile::TempPath>)> {
726+
fn write_to_temp(
727+
&self,
728+
bytes: &[u8],
729+
ext: &str,
730+
) -> Result<(PathBuf, Option<tempfile::TempPath>)> {
710731
use std::io::Write;
711732

712-
let mut temp_file = tempfile::Builder::new().suffix(ext).tempfile()?;
733+
let temp_dir = self.make_temp_dir()?;
734+
let mut temp_file = tempfile::Builder::new()
735+
.prefix(&format!("{}-{}-installer", self.app_name, self.version))
736+
.suffix(ext)
737+
.rand_bytes(0)
738+
.tempfile_in(temp_dir)?;
713739
temp_file.write_all(bytes)?;
714740

715741
let temp = temp_file.into_temp_path();

plugins/updater/tests/app-updater/tauri.conf.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"bundle": {
1414
"active": true,
1515
"targets": "all",
16+
"createUpdaterArtifacts": true,
1617
"icon": [
1718
"icons/32x32.png",
1819
"icons/128x128.png",
@@ -23,6 +24,9 @@
2324
"windows": {
2425
"webviewInstallMode": {
2526
"type": "skip"
27+
},
28+
"nsis": {
29+
"compression": "none"
2630
}
2731
}
2832
}

plugins/updater/tests/app-updater/tests/update.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,22 +162,25 @@ fn update_app() {
162162
// bundle app update
163163
build_app(&manifest_dir, &config, true, Default::default());
164164

165-
let updater_zip_ext = if cfg!(windows) { "zip" } else { "tar.gz" };
165+
let updater_zip_ext = if cfg!(target_os = "macos") {
166+
Some("tar.gz")
167+
} else {
168+
None
169+
};
166170

167171
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
168-
let bundle_updater_ext = out_bundle_path
169-
.extension()
170-
.unwrap()
171-
.to_str()
172-
.unwrap()
173-
.replace("exe", "nsis");
174-
let signature_path =
175-
out_bundle_path.with_extension(format!("{bundle_updater_ext}.{updater_zip_ext}.sig"));
172+
let bundle_updater_ext = out_bundle_path.extension().unwrap().to_str().unwrap();
173+
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
174+
format!("{bundle_updater_ext}.{updater_zip_ext}")
175+
} else {
176+
format!("{bundle_updater_ext}")
177+
};
178+
let signature_extension = format!("{updater_extension}.sig");
179+
let signature_path = out_bundle_path.with_extension(signature_extension);
176180
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
177181
panic!("failed to read signature file {}", signature_path.display())
178182
});
179-
let out_updater_path =
180-
out_bundle_path.with_extension(format!("{}.{}", bundle_updater_ext, updater_zip_ext));
183+
let out_updater_path = out_bundle_path.with_extension(updater_extension);
181184
let updater_path = root_dir.join(format!(
182185
"target/debug/{}",
183186
out_updater_path.file_name().unwrap().to_str().unwrap()

0 commit comments

Comments
 (0)