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 { impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
fn updater_builder(&self) -> UpdaterBuilder { fn updater_builder(&self) -> UpdaterBuilder {
let app = self.app_handle(); 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 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 { if let Some(target) = target {
builder = builder.target(target); builder = builder.target(target);

@ -94,6 +94,7 @@ impl RemoteRelease {
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>; pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
pub struct UpdaterBuilder { pub struct UpdaterBuilder {
app_name: String,
current_version: Version, current_version: Version,
config: Config, config: Config,
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>, version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
@ -109,7 +110,9 @@ pub struct UpdaterBuilder {
} }
impl 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 { Self {
installer_args: config installer_args: config
.windows .windows
@ -117,6 +120,7 @@ impl UpdaterBuilder {
.map(|w| w.installer_args.clone()) .map(|w| w.installer_args.clone())
.unwrap_or_default(), .unwrap_or_default(),
current_exe_args: Vec::new(), current_exe_args: Vec::new(),
app_name,
current_version, current_version,
config, config,
version_comparator: None, version_comparator: None,
@ -239,6 +243,7 @@ impl UpdaterBuilder {
Ok(Updater { Ok(Updater {
config: self.config, config: self.config,
app_name: self.app_name,
current_version: self.current_version, current_version: self.current_version,
version_comparator: self.version_comparator, version_comparator: self.version_comparator,
timeout: self.timeout, timeout: self.timeout,
@ -270,6 +275,7 @@ impl UpdaterBuilder {
pub struct Updater { pub struct Updater {
config: Config, config: Config,
app_name: String,
current_version: Version, current_version: Version,
version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>, version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,
timeout: Option<Duration>, timeout: Option<Duration>,
@ -386,6 +392,7 @@ impl Updater {
Some(Update { Some(Update {
config: self.config.clone(), config: self.config.clone(),
on_before_exit: self.on_before_exit.clone(), on_before_exit: self.on_before_exit.clone(),
app_name: self.app_name.clone(),
current_version: self.current_version.to_string(), current_version: self.current_version.to_string(),
target: self.target.clone(), target: self.target.clone(),
extract_path: self.extract_path.clone(), extract_path: self.extract_path.clone(),
@ -436,6 +443,9 @@ pub struct Update {
/// Extract path /// Extract path
#[allow(unused)] #[allow(unused)]
extract_path: PathBuf, extract_path: PathBuf,
/// App name, used for creating named tempfiles on Windows
#[allow(unused)]
app_name: String,
#[allow(unused)] #[allow(unused)]
installer_args: Vec<OsString>, installer_args: Vec<OsString>,
#[allow(unused)] #[allow(unused)]
@ -584,7 +594,7 @@ impl Update {
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW}, 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 install_mode = self.config.install_mode();
let current_args = &self.current_exe_args()[1..]; let current_args = &self.current_exe_args()[1..];
@ -663,24 +673,31 @@ impl Update {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
fn extract(bytes: &[u8]) -> Result<WindowsUpdaterType> { fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
#[cfg(feature = "zip")] #[cfg(feature = "zip")]
if infer::archive::is_zip(bytes) { 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")] #[cfg(feature = "zip")]
fn extract_zip(bytes: &[u8]) -> Result<WindowsUpdaterType> { fn extract_zip(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
let tmp_dir = tempfile::Builder::new().tempdir()?.into_path(); let temp_dir = self.make_temp_dir()?;
let archive = Cursor::new(bytes); let archive = Cursor::new(bytes);
let mut extractor = zip::ZipArchive::new(archive)?; 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 { for path in paths {
let path = path?.path(); let path = path?.path();
let ext = path.extension(); let ext = path.extension();
@ -694,22 +711,31 @@ impl Update {
Err(crate::Error::BinaryNotFoundInArchive) 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) { 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)) Ok(WindowsUpdaterType::nsis(path, temp))
} else if infer::archive::is_msi(bytes) { } 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)) Ok(WindowsUpdaterType::msi(path, temp))
} else { } else {
Err(crate::Error::InvalidUpdaterFormat) 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; 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)?; temp_file.write_all(bytes)?;
let temp = temp_file.into_temp_path(); let temp = temp_file.into_temp_path();

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

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

Loading…
Cancel
Save