mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-08-17 19:07:12 +02:00
Merge branch 'v2' of github.com:kandrelczyk/plugins-workspace into feature/fallback_targets
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user