Merge branch 'v2' of github.com:kandrelczyk/plugins-workspace into feature/fallback_targets

This commit is contained in:
Krzysztof Andrelczyk
2025-04-10 23:16:14 +02:00
176 changed files with 4691 additions and 3644 deletions
+23 -14
View File
@@ -28,8 +28,7 @@ pub enum DownloadEvent {
#[derive(Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Metadata {
rid: Option<ResourceId>,
available: bool,
rid: ResourceId,
current_version: String,
version: String,
date: Option<String>,
@@ -47,7 +46,7 @@ pub(crate) async fn check<R: Runtime>(
timeout: Option<u64>,
proxy: Option<String>,
target: Option<String>,
) -> Result<Metadata> {
) -> Result<Option<Metadata>> {
let mut builder = webview.updater_builder();
if let Some(headers) = headers {
for (k, v) in headers {
@@ -67,18 +66,28 @@ pub(crate) async fn check<R: Runtime>(
let updater = builder.build()?;
let update = updater.check().await?;
let mut metadata = Metadata::default();
if let Some(update) = update {
metadata.available = true;
metadata.current_version.clone_from(&update.current_version);
metadata.version.clone_from(&update.version);
metadata.date = update.date.map(|d| d.to_string());
metadata.body.clone_from(&update.body);
metadata.raw_json.clone_from(&update.raw_json);
metadata.rid = Some(webview.resources_table().add(update));
}
Ok(metadata)
if let Some(update) = update {
let formatted_date = if let Some(date) = update.date {
let formatted_date = date
.format(&time::format_description::well_known::Rfc3339)
.map_err(|_| crate::Error::FormatDate)?;
Some(formatted_date)
} else {
None
};
let metadata = Metadata {
current_version: update.current_version.clone(),
version: update.version.clone(),
date: formatted_date,
body: update.body.clone(),
raw_json: update.raw_json.clone(),
rid: webview.resources_table().add(update),
};
Ok(Some(metadata))
} else {
Ok(None)
}
}
#[tauri::command]
+2
View File
@@ -83,6 +83,8 @@ pub enum Error {
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
#[error(transparent)]
InvalidHeaderName(#[from] http::header::InvalidHeaderName),
#[error("Failed to format date")]
FormatDate,
/// The configured updater endpoint must use a secure protocol like `https`
#[error("The configured updater endpoint must use a secure protocol like `https`.")]
InsecureTransportProtocol,
+2 -3
View File
@@ -159,8 +159,7 @@ impl Builder {
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
let args = args.into_iter().map(|a| a.into()).collect::<Vec<_>>();
self.installer_args.extend_from_slice(&args);
self.installer_args.extend(args.into_iter().map(Into::into));
self
}
@@ -220,7 +219,7 @@ impl Builder {
config.pubkey = pubkey;
}
if let Some(windows) = &mut config.windows {
windows.installer_args.extend_from_slice(&installer_args);
windows.installer_args.extend(installer_args);
}
app.manage(UpdaterState {
target,
+69 -13
View File
@@ -4,7 +4,7 @@
use std::{
collections::HashMap,
ffi::{OsStr, OsString},
ffi::OsString,
io::Cursor,
path::{Path, PathBuf},
str::FromStr,
@@ -12,6 +12,9 @@ use std::{
time::Duration,
};
#[cfg(not(target_os = "macos"))]
use std::ffi::OsStr;
use base64::Engine;
use futures_util::StreamExt;
use http::HeaderName;
@@ -131,6 +134,7 @@ impl RemoteRelease {
}
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
pub type OnBeforeRequest = Arc<dyn Fn(ClientBuilder) -> ClientBuilder + Send + Sync + 'static>;
pub type VersionComparator = Arc<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>;
type MainThreadClosure = Box<dyn FnOnce() + Send + Sync + 'static>;
type RunOnMainThread =
@@ -152,13 +156,13 @@ pub struct UpdaterBuilder {
installer_args: Vec<OsString>,
current_exe_args: Vec<OsString>,
on_before_exit: Option<OnBeforeExit>,
configure_client: Option<OnBeforeRequest>,
}
impl UpdaterBuilder {
pub(crate) fn new<R: Runtime>(app: &AppHandle<R>, config: crate::Config) -> Self {
let app_ = app.clone();
let run_on_main_thread =
move |f: Box<dyn FnOnce() + Send + Sync + 'static>| app_.run_on_main_thread(f);
let run_on_main_thread = move |f| app_.run_on_main_thread(f);
Self {
run_on_main_thread: Box::new(run_on_main_thread),
installer_args: config
@@ -178,6 +182,7 @@ impl UpdaterBuilder {
timeout: None,
proxy: None,
on_before_exit: None,
configure_client: None,
}
}
@@ -262,8 +267,7 @@ impl UpdaterBuilder {
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
let args = args.into_iter().map(|a| a.into()).collect::<Vec<_>>();
self.installer_args.extend_from_slice(&args);
self.installer_args.extend(args.into_iter().map(Into::into));
self
}
@@ -277,6 +281,19 @@ impl UpdaterBuilder {
self
}
/// Allows you to modify the `reqwest` client builder before the HTTP request is sent.
///
/// Note that `reqwest` crate may be updated in minor releases of tauri-plugin-updater.
/// Therefore it's recommended to pin the plugin to at least a minor version when you're using `configure_client`.
///
pub fn configure_client<F: Fn(ClientBuilder) -> ClientBuilder + Send + Sync + 'static>(
mut self,
f: F,
) -> Self {
self.configure_client.replace(Arc::new(f));
self
}
pub fn build(self) -> Result<Updater> {
let endpoints = self
.endpoints
@@ -321,6 +338,7 @@ impl UpdaterBuilder {
headers: self.headers,
extract_path,
on_before_exit: self.on_before_exit,
configure_client: self.configure_client,
})
}
}
@@ -331,8 +349,8 @@ impl UpdaterBuilder {
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
let args = args.into_iter().map(|a| a.into()).collect::<Vec<_>>();
self.current_exe_args.extend_from_slice(&args);
self.current_exe_args
.extend(args.into_iter().map(Into::into));
self
}
}
@@ -355,6 +373,7 @@ pub struct Updater {
headers: HeaderMap,
extract_path: PathBuf,
on_before_exit: Option<OnBeforeExit>,
configure_client: Option<OnBeforeRequest>,
#[allow(unused)]
installer_args: Vec<OsString>,
#[allow(unused)]
@@ -417,14 +436,22 @@ impl Updater {
.replace("{{arch}}", self.arch)
.parse()?;
log::debug!("checking for updates {url}");
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if let Some(timeout) = self.timeout {
request = request.timeout(timeout);
}
if let Some(ref proxy) = self.proxy {
log::debug!("using proxy {proxy}");
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
}
if let Some(ref configure_client) = self.configure_client {
request = configure_client(request);
}
let response = request
.build()?
.get(url)
@@ -437,24 +464,38 @@ impl Updater {
if res.status().is_success() {
// no updates found!
if StatusCode::NO_CONTENT == res.status() {
log::debug!("update endpoint returned 204 No Content");
return Ok(None);
};
raw_json = Some(res.json().await?);
match serde_json::from_value::<RemoteRelease>(raw_json.clone().unwrap())
let update_response: serde_json::Value = res.json().await?;
log::debug!("update response: {update_response:?}");
raw_json = Some(update_response.clone());
match serde_json::from_value::<RemoteRelease>(update_response)
.map_err(Into::into)
{
Ok(release) => {
log::debug!("parsed release response {release:?}");
last_error = None;
remote_release = Some(release);
// we found a relase, break the loop
// we found a release, break the loop
break;
}
Err(err) => last_error = Some(err),
Err(err) => {
log::error!("failed to deserialize update response: {err}");
last_error = Some(err)
}
}
} else {
log::error!(
"update endpoint did not respond with a successful status code"
);
}
}
Err(err) => last_error = Some(err.into()),
Err(err) => {
log::error!("failed to check for updates: {err}");
last_error = Some(err.into())
}
}
}
@@ -494,11 +535,12 @@ impl Updater {
.to_owned(),
installer,
raw_json: raw_json.unwrap(),
timeout: self.timeout,
timeout: None,
proxy: self.proxy.clone(),
headers: self.headers.clone(),
installer_args: self.installer_args.clone(),
current_exe_args: self.current_exe_args.clone(),
configure_client: self.configure_client.clone(),
})
} else {
None
@@ -549,6 +591,7 @@ pub struct Update {
installer_args: Vec<OsString>,
#[allow(unused)]
current_exe_args: Vec<OsString>,
configure_client: Option<OnBeforeRequest>,
}
impl Resource for Update {}
@@ -577,6 +620,9 @@ impl Update {
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
}
if let Some(ref configure_client) = self.configure_client {
request = configure_client(request);
}
let response = request
.build()?
.get(self.download_url.clone())
@@ -725,6 +771,7 @@ impl Update {
};
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
log::debug!("running on_before_exit hook");
on_before_exit();
}
@@ -892,6 +939,7 @@ impl Update {
#[cfg(feature = "zip")]
if infer::archive::is_gz(bytes) {
log::debug!("extracting AppImage");
// extract the buffer to the tmp_dir
// we extract our signed archive into our final directory without any temp file
let archive = Cursor::new(bytes);
@@ -915,6 +963,7 @@ impl Update {
return Err(Error::BinaryNotFoundInArchive);
}
log::debug!("rewriting AppImage");
return match std::fs::write(&self.extract_path, bytes)
.and_then(|_| std::fs::set_permissions(&self.extract_path, permissions))
{
@@ -935,6 +984,7 @@ impl Update {
fn install_deb(&self, bytes: &[u8]) -> Result<()> {
// First verify the bytes are actually a .deb package
if !infer::archive::is_deb(bytes) {
log::warn!("update is not a valid deb package");
return Err(Error::InvalidUpdaterFormat);
}
@@ -998,6 +1048,7 @@ impl Update {
.status()
{
if status.success() {
log::debug!("installed deb with pkexec");
return Ok(());
}
}
@@ -1005,6 +1056,7 @@ impl Update {
// 2. Try zenity or kdialog for a graphical sudo experience
if let Ok(password) = self.get_password_graphically() {
if self.install_with_sudo(pkg_path, &password, install_cmd, install_arg)? {
log::debug!("installed deb with GUI sudo");
return Ok(());
}
}
@@ -1017,6 +1069,7 @@ impl Update {
.status()?;
if status.success() {
log::debug!("installed deb with sudo");
Ok(())
} else {
Err(Error::PackageInstallFailed)
@@ -1146,6 +1199,7 @@ impl Update {
};
if need_authorization {
log::debug!("app installation needs admin privileges");
// Use AppleScript to perform moves with admin privileges
let apple_script = format!(
"do shell script \"rm -rf '{src}' && mv -f '{new}' '{src}'\" with administrator privileges",
@@ -1218,6 +1272,8 @@ pub(crate) fn get_updater_arch() -> Option<&'static str> {
Some("armv7")
} else if cfg!(target_arch = "aarch64") {
Some("aarch64")
} else if cfg!(target_arch = "riscv64") {
Some("riscv64")
} else {
None
}