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
pull/1441/head
Tony 11 months ago committed by GitHub
parent 77ee644afd
commit f83b9e9813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,7 @@
---
"updater": patch
---
On Windows, use a named tempfile with `<app name>-<version>-installer.exe` (or `.msi`) for v2 updater
**Breaking Change**: `UpdaterBuilder::new` now takes one more argument `app_name: String`

@ -70,10 +70,14 @@ pub trait UpdaterExt<R: Runtime> {
impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
fn updater_builder(&self) -> UpdaterBuilder {
let app = self.app_handle();
let version = app.package_info().version.clone();
let package_info = app.package_info();
let UpdaterState { config, target } = self.state::<UpdaterState>().inner();
let mut builder = UpdaterBuilder::new(version, config.clone());
let mut builder = UpdaterBuilder::new(
package_info.name.clone(),
package_info.version.clone(),
config.clone(),
);
if let Some(target) = target {
builder = builder.target(target);

@ -94,6 +94,7 @@ impl RemoteRelease {
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
pub struct UpdaterBuilder {
app_name: String,
current_version: Version,
config: Config,
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
@ -109,7 +110,9 @@ pub struct UpdaterBuilder {
}
impl UpdaterBuilder {
pub fn new(current_version: Version, config: crate::Config) -> Self {
/// It's prefered to use [`crate::UpdaterExt::updater_builder`] instead of
/// constructing a [`UpdaterBuilder`] with this function yourself
pub fn new(app_name: String, current_version: Version, config: crate::Config) -> Self {
Self {
installer_args: config
.windows
@ -117,6 +120,7 @@ impl UpdaterBuilder {
.map(|w| w.installer_args.clone())
.unwrap_or_default(),
current_exe_args: Vec::new(),
app_name,
current_version,
config,
version_comparator: None,
@ -239,6 +243,7 @@ impl UpdaterBuilder {
Ok(Updater {
config: self.config,
app_name: self.app_name,
current_version: self.current_version,
version_comparator: self.version_comparator,
timeout: self.timeout,
@ -270,6 +275,7 @@ impl UpdaterBuilder {
pub struct Updater {
config: Config,
app_name: String,
current_version: Version,
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
timeout: Option<Duration>,
@ -386,6 +392,7 @@ impl Updater {
Some(Update {
config: self.config.clone(),
on_before_exit: self.on_before_exit.clone(),
app_name: self.app_name.clone(),
current_version: self.current_version.to_string(),
target: self.target.clone(),
extract_path: self.extract_path.clone(),
@ -436,6 +443,9 @@ pub struct Update {
/// Extract path
#[allow(unused)]
extract_path: PathBuf,
/// App name, used for creating named tempfiles on Windows
#[allow(unused)]
app_name: String,
#[allow(unused)]
installer_args: Vec<OsString>,
#[allow(unused)]
@ -584,7 +594,7 @@ impl Update {
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
};
let updater_type = Self::extract(bytes)?;
let updater_type = self.extract(bytes)?;
let install_mode = self.config.install_mode();
let current_args = &self.current_exe_args()[1..];
@ -663,24 +673,31 @@ impl Update {
.collect::<Vec<_>>()
}
fn extract(bytes: &[u8]) -> Result<WindowsUpdaterType> {
fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
#[cfg(feature = "zip")]
if infer::archive::is_zip(bytes) {
return Self::extract_zip(bytes);
return self.extract_zip(bytes);
}
Self::extract_exe(bytes)
self.extract_exe(bytes)
}
fn make_temp_dir(&self) -> Result<PathBuf> {
Ok(tempfile::Builder::new()
.prefix(&format!("{}-{}-updater-", self.app_name, self.version))
.tempdir()?
.into_path())
}
#[cfg(feature = "zip")]
fn extract_zip(bytes: &[u8]) -> Result<WindowsUpdaterType> {
let tmp_dir = tempfile::Builder::new().tempdir()?.into_path();
fn extract_zip(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
let temp_dir = self.make_temp_dir()?;
let archive = Cursor::new(bytes);
let mut extractor = zip::ZipArchive::new(archive)?;
extractor.extract(&tmp_dir)?;
extractor.extract(&temp_dir)?;
let paths = std::fs::read_dir(&tmp_dir)?;
let paths = std::fs::read_dir(&temp_dir)?;
for path in paths {
let path = path?.path();
let ext = path.extension();
@ -694,22 +711,31 @@ impl Update {
Err(crate::Error::BinaryNotFoundInArchive)
}
fn extract_exe(bytes: &[u8]) -> Result<WindowsUpdaterType> {
fn extract_exe(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
if infer::app::is_exe(bytes) {
let (path, temp) = Self::write_to_temp(bytes, ".exe")?;
let (path, temp) = self.write_to_temp(bytes, ".exe")?;
Ok(WindowsUpdaterType::nsis(path, temp))
} else if infer::archive::is_msi(bytes) {
let (path, temp) = Self::write_to_temp(bytes, ".msi")?;
let (path, temp) = self.write_to_temp(bytes, ".msi")?;
Ok(WindowsUpdaterType::msi(path, temp))
} else {
Err(crate::Error::InvalidUpdaterFormat)
}
}
fn write_to_temp(bytes: &[u8], ext: &str) -> Result<(PathBuf, Option<tempfile::TempPath>)> {
fn write_to_temp(
&self,
bytes: &[u8],
ext: &str,
) -> Result<(PathBuf, Option<tempfile::TempPath>)> {
use std::io::Write;
let mut temp_file = tempfile::Builder::new().suffix(ext).tempfile()?;
let temp_dir = self.make_temp_dir()?;
let mut temp_file = tempfile::Builder::new()
.prefix(&format!("{}-{}-installer", self.app_name, self.version))
.suffix(ext)
.rand_bytes(0)
.tempfile_in(temp_dir)?;
temp_file.write_all(bytes)?;
let temp = temp_file.into_temp_path();

@ -13,6 +13,7 @@
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@ -23,6 +24,9 @@
"windows": {
"webviewInstallMode": {
"type": "skip"
},
"nsis": {
"compression": "none"
}
}
}

@ -162,22 +162,25 @@ fn update_app() {
// bundle app update
build_app(&manifest_dir, &config, true, Default::default());
let updater_zip_ext = if cfg!(windows) { "zip" } else { "tar.gz" };
let updater_zip_ext = if cfg!(target_os = "macos") {
Some("tar.gz")
} else {
None
};
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
let bundle_updater_ext = out_bundle_path
.extension()
.unwrap()
.to_str()
.unwrap()
.replace("exe", "nsis");
let signature_path =
out_bundle_path.with_extension(format!("{bundle_updater_ext}.{updater_zip_ext}.sig"));
let bundle_updater_ext = out_bundle_path.extension().unwrap().to_str().unwrap();
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
format!("{bundle_updater_ext}.{updater_zip_ext}")
} else {
format!("{bundle_updater_ext}")
};
let signature_extension = format!("{updater_extension}.sig");
let signature_path = out_bundle_path.with_extension(signature_extension);
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
panic!("failed to read signature file {}", signature_path.display())
});
let out_updater_path =
out_bundle_path.with_extension(format!("{}.{}", bundle_updater_ext, updater_zip_ext));
let out_updater_path = out_bundle_path.with_extension(updater_extension);
let updater_path = root_dir.join(format!(
"target/debug/{}",
out_updater_path.file_name().unwrap().to_str().unwrap()

Loading…
Cancel
Save