mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-09-04 20:16:34 +02:00
feat(updater): support non zipped updater (#1174)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"updater": "patch"
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for updating using non-zipped files on Windows and Linux.
|
||||||
Generated
+2
-46
@@ -334,16 +334,6 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "assert-json-diff"
|
|
||||||
version = "2.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -1146,16 +1136,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colored"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"windows-sys 0.48.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.6"
|
version = "4.6.6"
|
||||||
@@ -1893,7 +1873,7 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
|
checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colored 1.9.4",
|
"colored",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3469,24 +3449,6 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mockito"
|
|
||||||
version = "0.31.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "80f9fece9bd97ab74339fe19f4bcaf52b76dcc18e5364c7977c1838f76b38de9"
|
|
||||||
dependencies = [
|
|
||||||
"assert-json-diff",
|
|
||||||
"colored 2.1.0",
|
|
||||||
"httparse",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"regex",
|
|
||||||
"serde_json",
|
|
||||||
"serde_urlencoded",
|
|
||||||
"similar",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "muda"
|
name = "muda"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -5308,12 +5270,6 @@ version = "0.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
|
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "similar"
|
|
||||||
version = "2.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "single-instance-example"
|
name = "single-instance-example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -6460,8 +6416,8 @@ dependencies = [
|
|||||||
"flate2",
|
"flate2",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
|
"infer",
|
||||||
"minisign-verify",
|
"minisign-verify",
|
||||||
"mockito",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
isChecking = true;
|
isChecking = true;
|
||||||
try {
|
try {
|
||||||
const update = await check();
|
const update = await check();
|
||||||
onMessage(`Should update: ${update.response.available}`);
|
onMessage(`Should update: ${update.available}`);
|
||||||
onMessage(update.response);
|
onMessage(update);
|
||||||
|
|
||||||
newUpdate = update;
|
newUpdate = update;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+18
-12
@@ -25,27 +25,33 @@ tokio = "1"
|
|||||||
reqwest = { version = "0.12", default-features = false, features = [ "json", "stream" ] }
|
reqwest = { version = "0.12", default-features = false, features = [ "json", "stream" ] }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
http = "1"
|
http = "1"
|
||||||
dirs-next = "2"
|
|
||||||
minisign-verify = "0.2"
|
minisign-verify = "0.2"
|
||||||
time = { version = "0.3", features = [ "parsing", "formatting" ] }
|
time = { version = "0.3", features = [ "parsing", "formatting" ] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
semver = { version = "1", features = [ "serde" ] }
|
semver = { version = "1", features = [ "serde" ] }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
zip = "0.6"
|
infer = "0.15"
|
||||||
|
|
||||||
[target."cfg(target_os = \"windows\")".dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows-sys = { version = "0.52.0", features = [ "Win32_Foundation", "Win32_UI_WindowsAndMessaging" ] }
|
zip = { version = "0.6", optional = true }
|
||||||
|
windows-sys = { version = "0.52.0", features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
] }
|
||||||
|
|
||||||
[target."cfg(any(target_os = \"macos\", target_os = \"linux\"))".dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
dirs-next = "2"
|
||||||
|
tar = { version = "0.4", optional = true }
|
||||||
|
flate2 = { version = "1", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
mockito = "0.31"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "rustls-tls" ]
|
default = ["rustls-tls", "zip"]
|
||||||
native-tls = [ "reqwest/native-tls" ]
|
zip = ["dep:zip", "dep:tar", "dep:flate2"]
|
||||||
native-tls-vendored = [ "reqwest/native-tls-vendored" ]
|
native-tls = ["reqwest/native-tls"]
|
||||||
rustls-tls = [ "reqwest/rustls-tls" ]
|
native-tls-vendored = ["reqwest/native-tls-vendored"]
|
||||||
|
rustls-tls = ["reqwest/rustls-tls"]
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ pub enum Error {
|
|||||||
/// UTF8 Errors in signature.
|
/// UTF8 Errors in signature.
|
||||||
#[error("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")]
|
#[error("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")]
|
||||||
SignatureUtf8(String),
|
SignatureUtf8(String),
|
||||||
|
#[cfg(all(target_os = "windows", feature = "zip"))]
|
||||||
/// `zip` errors.
|
/// `zip` errors.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Extract(#[from] zip::result::ZipError),
|
Extract(#[from] zip::result::ZipError),
|
||||||
@@ -62,6 +63,8 @@ pub enum Error {
|
|||||||
TempDirNotOnSameMountPoint,
|
TempDirNotOnSameMountPoint,
|
||||||
#[error("binary for the current target not found in the archive")]
|
#[error("binary for the current target not found in the archive")]
|
||||||
BinaryNotFoundInArchive,
|
BinaryNotFoundInArchive,
|
||||||
|
#[error("invalid updater binary format")]
|
||||||
|
InvalidUpdaterFormat,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Http(#[from] http::Error),
|
Http(#[from] http::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|||||||
+180
-115
@@ -475,9 +475,8 @@ impl Update {
|
|||||||
let mut stream = response.bytes_stream();
|
let mut stream = response.bytes_stream();
|
||||||
while let Some(chunk) = stream.next().await {
|
while let Some(chunk) = stream.next().await {
|
||||||
let chunk = chunk?;
|
let chunk = chunk?;
|
||||||
let bytes = chunk.as_ref().to_vec();
|
on_chunk(chunk.len(), content_length);
|
||||||
on_chunk(bytes.len(), content_length);
|
buffer.extend(chunk);
|
||||||
buffer.extend(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
on_download_finish();
|
on_download_finish();
|
||||||
@@ -508,114 +507,167 @@ impl Update {
|
|||||||
fn install_inner(&self, _bytes: Vec<u8>) -> Result<()> {
|
fn install_inner(&self, _bytes: Vec<u8>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Windows
|
#[cfg(windows)]
|
||||||
//
|
enum WindowsUpdaterType {
|
||||||
// ### Expected structure:
|
Nsis,
|
||||||
// ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler
|
Msi,
|
||||||
// │ └──[AppName]_[version]_x64.msi # Application MSI
|
}
|
||||||
// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler
|
|
||||||
// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
|
#[cfg(windows)]
|
||||||
// └── ...
|
impl WindowsUpdaterType {
|
||||||
//
|
fn extension(&self) -> &str {
|
||||||
// ## MSI
|
match self {
|
||||||
// Update server can provide a MSI for Windows. (Generated with tauri-bundler from *Wix*)
|
WindowsUpdaterType::Nsis => ".exe",
|
||||||
// To replace current version of the application. In later version we'll offer
|
WindowsUpdaterType::Msi => ".msi",
|
||||||
// incremental update to push specific binaries.
|
}
|
||||||
//
|
}
|
||||||
// ## EXE
|
}
|
||||||
// Update server can provide a custom EXE (installer) who can run any task.
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
impl Config {
|
||||||
|
fn install_mode(&self) -> crate::config::WindowsUpdateInstallMode {
|
||||||
|
self.windows
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| w.install_mode.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Windows
|
||||||
|
#[cfg(windows)]
|
||||||
|
impl Update {
|
||||||
|
/// ### Expected structure:
|
||||||
|
/// ├── [AppName]_[version]_x64.msi # Application MSI
|
||||||
|
/// ├── [AppName]_[version]_x64-setup.exe # NSIS installer
|
||||||
|
/// ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler
|
||||||
|
/// │ └──[AppName]_[version]_x64.msi # Application MSI
|
||||||
|
/// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler
|
||||||
|
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
|
||||||
|
/// └── ...
|
||||||
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
||||||
use std::fs;
|
|
||||||
use windows_sys::{
|
use windows_sys::{
|
||||||
w,
|
w,
|
||||||
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
|
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: We need to create a memory buffer with the MSI and then run it.
|
let (updater_type, path, _temp) = Self::extract(&bytes)?;
|
||||||
// (instead of extracting the MSI to a temp path)
|
|
||||||
//
|
|
||||||
// The tricky part is the MSI need to be exposed and spawned so the memory allocation
|
|
||||||
// shouldn't drop but we should be able to pass the reference so we can drop it once the installation
|
|
||||||
// is done, otherwise we have a huge memory leak.
|
|
||||||
|
|
||||||
|
let install_mode = self.config.install_mode();
|
||||||
|
let mut installer_args = self.installer_args();
|
||||||
|
match updater_type {
|
||||||
|
WindowsUpdaterType::Nsis => {
|
||||||
|
installer_args.extend(install_mode.nsis_args().iter().map(OsStr::new));
|
||||||
|
}
|
||||||
|
WindowsUpdaterType::Msi => {
|
||||||
|
installer_args.extend(install_mode.msiexec_args().iter().map(OsStr::new));
|
||||||
|
installer_args.push(OsStr::new("/promptrestart"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
|
||||||
|
on_before_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = encode_wide(path);
|
||||||
|
let parameters = encode_wide(installer_args.join(OsStr::new(" ")));
|
||||||
|
unsafe {
|
||||||
|
ShellExecuteW(
|
||||||
|
0,
|
||||||
|
w!("open"),
|
||||||
|
file.as_ptr(),
|
||||||
|
parameters.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
SW_SHOW,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn installer_args(&self) -> Vec<&OsStr> {
|
||||||
|
self.installer_args
|
||||||
|
.iter()
|
||||||
|
.map(OsStr::new)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract(bytes: &[u8]) -> Result<(WindowsUpdaterType, PathBuf, Option<tempfile::TempPath>)> {
|
||||||
|
#[cfg(feature = "zip")]
|
||||||
|
if infer::archive::is_zip(bytes) {
|
||||||
|
return Self::extract_zip(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::extract_exe(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "zip")]
|
||||||
|
fn extract_zip(
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> Result<(WindowsUpdaterType, PathBuf, Option<tempfile::TempPath>)> {
|
||||||
let tmp_dir = tempfile::Builder::new().tempdir()?.into_path();
|
let tmp_dir = tempfile::Builder::new().tempdir()?.into_path();
|
||||||
|
|
||||||
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(&tmp_dir)?;
|
||||||
|
|
||||||
let paths = fs::read_dir(&tmp_dir)?;
|
let paths = std::fs::read_dir(&tmp_dir)?;
|
||||||
|
|
||||||
let install_mode = self
|
|
||||||
.config
|
|
||||||
.windows
|
|
||||||
.as_ref()
|
|
||||||
.map(|w| w.install_mode.clone())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let mut installer_args = self
|
|
||||||
.installer_args
|
|
||||||
.iter()
|
|
||||||
.map(OsStr::new)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let found_path = path?.path();
|
let found_path = path?.path();
|
||||||
// we support 2 type of files exe & msi for now
|
let ext = found_path.extension();
|
||||||
// If it's an `exe` we expect an NSIS installer.
|
if ext == Some(OsStr::new("exe")) {
|
||||||
if found_path.extension() == Some(OsStr::new("exe")) {
|
return Ok((WindowsUpdaterType::Nsis, found_path, None));
|
||||||
installer_args.extend(install_mode.nsis_args().iter().map(OsStr::new));
|
} else if ext == Some(OsStr::new("msi")) {
|
||||||
} else if found_path.extension() == Some(OsStr::new("msi")) {
|
return Ok((WindowsUpdaterType::Msi, found_path, None));
|
||||||
installer_args.extend(install_mode.msiexec_args().iter().map(OsStr::new));
|
|
||||||
installer_args.push(OsStr::new("/promptrestart"));
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
|
|
||||||
on_before_exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = encode_wide(found_path.as_os_str());
|
|
||||||
let parameters = encode_wide(installer_args.join(OsStr::new(" ")).as_os_str());
|
|
||||||
unsafe {
|
|
||||||
ShellExecuteW(
|
|
||||||
0,
|
|
||||||
w!("open"),
|
|
||||||
file.as_ptr(),
|
|
||||||
parameters.as_ptr(),
|
|
||||||
std::ptr::null(),
|
|
||||||
SW_SHOW,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Err(crate::Error::BinaryNotFoundInArchive)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Linux (AppImage)
|
fn extract_exe(
|
||||||
//
|
bytes: &[u8],
|
||||||
// ### Expected structure:
|
) -> Result<(WindowsUpdaterType, PathBuf, Option<tempfile::TempPath>)> {
|
||||||
// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
|
use std::io::Write;
|
||||||
// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
|
|
||||||
// └── ...
|
let updater_type = if infer::app::is_exe(bytes) {
|
||||||
//
|
WindowsUpdaterType::Nsis
|
||||||
// We should have an AppImage already installed to be able to copy and install
|
} else if infer::archive::is_msi(bytes) {
|
||||||
// the extract_path is the current AppImage path
|
WindowsUpdaterType::Msi
|
||||||
// tmp_dir is where our new AppImage is found
|
} else {
|
||||||
#[cfg(any(
|
return Err(crate::Error::InvalidUpdaterFormat);
|
||||||
target_os = "linux",
|
};
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
let ext = updater_type.extension();
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
let mut temp_file = tempfile::Builder::new().suffix(ext).tempfile()?;
|
||||||
))]
|
temp_file.write_all(bytes)?;
|
||||||
|
let temp_path = temp_file.into_temp_path();
|
||||||
|
|
||||||
|
Ok((updater_type, temp_path.to_path_buf(), Some(temp_path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Linux (AppImage)
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
impl Update {
|
||||||
|
/// ### Expected structure:
|
||||||
|
/// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
|
||||||
|
/// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
|
||||||
|
/// └── ...
|
||||||
|
///
|
||||||
|
/// We should have an AppImage already installed to be able to copy and install
|
||||||
|
/// the extract_path is the current AppImage path
|
||||||
|
/// tmp_dir is where our new AppImage is found
|
||||||
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
||||||
use flate2::read::GzDecoder;
|
|
||||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
let archive = Cursor::new(bytes);
|
|
||||||
let extract_path_metadata = self.extract_path.metadata()?;
|
let extract_path_metadata = self.extract_path.metadata()?;
|
||||||
|
|
||||||
let tmp_dir_locations = vec![
|
let tmp_dir_locations = vec![
|
||||||
@@ -641,43 +693,56 @@ impl Update {
|
|||||||
// create a backup of our current app image
|
// create a backup of our current app image
|
||||||
std::fs::rename(&self.extract_path, tmp_app_image)?;
|
std::fs::rename(&self.extract_path, tmp_app_image)?;
|
||||||
|
|
||||||
// extract the buffer to the tmp_dir
|
#[cfg(feature = "zip")]
|
||||||
// we extract our signed archive into our final directory without any temp file
|
if infer::archive::is_gz(&bytes) {
|
||||||
let decoder = GzDecoder::new(archive);
|
// extract the buffer to the tmp_dir
|
||||||
let mut archive = tar::Archive::new(decoder);
|
// we extract our signed archive into our final directory without any temp file
|
||||||
for mut entry in archive.entries()?.flatten() {
|
let archive = Cursor::new(bytes);
|
||||||
if let Ok(path) = entry.path() {
|
let decoder = flate2::read::GzDecoder::new(archive);
|
||||||
if path.extension() == Some(OsStr::new("AppImage")) {
|
let mut archive = tar::Archive::new(decoder);
|
||||||
// if something went wrong during the extraction, we should restore previous app
|
for mut entry in archive.entries()?.flatten() {
|
||||||
if let Err(err) = entry.unpack(&self.extract_path) {
|
if let Ok(path) = entry.path() {
|
||||||
std::fs::rename(tmp_app_image, &self.extract_path)?;
|
if path.extension() == Some(OsStr::new("AppImage")) {
|
||||||
return Err(err.into());
|
// if something went wrong during the extraction, we should restore previous app
|
||||||
|
if let Err(err) = entry.unpack(&self.extract_path) {
|
||||||
|
std::fs::rename(tmp_app_image, &self.extract_path)?;
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
// early finish we have everything we need here
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
// early finish we have everything we need here
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if we have not returned early we should restore the backup
|
||||||
|
std::fs::rename(tmp_app_image, &self.extract_path)?;
|
||||||
|
return Err(Error::BinaryNotFoundInArchive);
|
||||||
}
|
}
|
||||||
// if we have not returned early we should restore the backup
|
|
||||||
std::fs::rename(tmp_app_image, &self.extract_path)?;
|
|
||||||
|
|
||||||
return Err(Error::BinaryNotFoundInArchive);
|
return match std::fs::write(&self.extract_path, bytes) {
|
||||||
|
Err(err) => {
|
||||||
|
// if something went wrong during the extraction, we should restore previous app
|
||||||
|
std::fs::rename(tmp_app_image, &self.extract_path)?;
|
||||||
|
Err(err.into())
|
||||||
|
}
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::TempDirNotOnSameMountPoint)
|
Err(Error::TempDirNotOnSameMountPoint)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MacOS
|
/// MacOS
|
||||||
//
|
#[cfg(target_os = "macos")]
|
||||||
// ### Expected structure:
|
impl Update {
|
||||||
// ├── [AppName]_[version]_x64.app.tar.gz # GZ generated by tauri-bundler
|
/// ### Expected structure:
|
||||||
// │ └──[AppName].app # Main application
|
/// ├── [AppName]_[version]_x64.app.tar.gz # GZ generated by tauri-bundler
|
||||||
// │ └── Contents # Application contents...
|
/// │ └──[AppName].app # Main application
|
||||||
// │ └── ...
|
/// │ └── Contents # Application contents...
|
||||||
// └── ...
|
/// │ └── ...
|
||||||
#[cfg(target_os = "macos")]
|
/// └── ...
|
||||||
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
|
|
||||||
@@ -889,7 +954,7 @@ fn base64_to_string(base64_string: &str) -> Result<String> {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(windows)]
|
||||||
fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
|
fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
|
||||||
use std::os::windows::ffi::OsStrExt;
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user