mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-27 11:56:05 +02:00
refactor(updater): migrate to tauri's new resource table (#899)
* refactor(updater): migrate to tauri's new resource table * fmt * fmt * Create updater-js-started-event.md
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"updater": "patch"
|
||||
"updater-js": "patch"
|
||||
---
|
||||
|
||||
Fix `Started` event not emitted to JS when downloading update.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"updater": "patch"
|
||||
---
|
||||
|
||||
**Breaking change**: Changed `Update::download` and `Update::download_and_install` first argument to take `FnMut` instead of just `Fn`.
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { invoke, Channel } from "@tauri-apps/api/core";
|
||||
import { invoke, Channel, Resource } from "@tauri-apps/api/core";
|
||||
|
||||
/** Options used to check for updates */
|
||||
interface CheckOptions {
|
||||
@@ -25,6 +25,7 @@ interface CheckOptions {
|
||||
}
|
||||
|
||||
interface UpdateMetadata {
|
||||
rid: number;
|
||||
available: boolean;
|
||||
currentVersion: string;
|
||||
version: string;
|
||||
@@ -38,7 +39,7 @@ type DownloadEvent =
|
||||
| { event: "Progress"; data: { chunkLength: number } }
|
||||
| { event: "Finished" };
|
||||
|
||||
class Update {
|
||||
class Update extends Resource {
|
||||
available: boolean;
|
||||
currentVersion: string;
|
||||
version: string;
|
||||
@@ -46,6 +47,7 @@ class Update {
|
||||
body?: string;
|
||||
|
||||
constructor(metadata: UpdateMetadata) {
|
||||
super(metadata.rid);
|
||||
this.available = metadata.available;
|
||||
this.currentVersion = metadata.currentVersion;
|
||||
this.version = metadata.version;
|
||||
@@ -58,11 +60,12 @@ class Update {
|
||||
onEvent?: (progress: DownloadEvent) => void,
|
||||
): Promise<void> {
|
||||
const channel = new Channel<DownloadEvent>();
|
||||
if (onEvent != null) {
|
||||
if (onEvent) {
|
||||
channel.onmessage = onEvent;
|
||||
}
|
||||
return invoke("plugin:updater|download_and_install", {
|
||||
onEvent: channel,
|
||||
rid: this.rid,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -73,9 +76,9 @@ async function check(options?: CheckOptions): Promise<Update | null> {
|
||||
options.headers = Array.from(new Headers(options.headers).entries());
|
||||
}
|
||||
|
||||
return invoke<UpdateMetadata>("plugin:updater|check", { ...options }).then(
|
||||
(meta) => (meta.available ? new Update(meta) : null),
|
||||
);
|
||||
return invoke<UpdateMetadata>("plugin:updater|check", {
|
||||
...options,
|
||||
}).then((meta) => (meta.available ? new Update(meta) : null));
|
||||
}
|
||||
|
||||
export type { CheckOptions, DownloadEvent };
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,r,n){if("a"===r&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}var r;"function"==typeof SuppressedError&&SuppressedError;class n{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,r.set(this,(()=>{})),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((e=>{t(this,r,"f").call(this,e)}))}set onmessage(e){!function(e,t,r,n,a){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!a)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!a:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");"a"===n?a.call(e,r):a?a.value=r:t.set(e,r)}(this,r,e,"f")}get onmessage(){return t(this,r,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}r=new WeakMap;class o{constructor(e){this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async downloadAndInstall(e){const t=new n;return null!=e&&(t.onmessage=e),a("plugin:updater|download_and_install",{onEvent:t})}}return e.Update=o,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),a("plugin:updater|check",{...e}).then((e=>e.available?new o(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function r(e,r,t,n){if("a"===t&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof r?e!==r||!n:!r.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?n:"a"===t?n.call(e):n?n.value:r.get(e)}function t(e,r,t,n,s){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!s)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof r?e!==r||!s:!r.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===n?s.call(e,t):s?s.value=t:r.set(e,t),t}var n,s;"function"==typeof SuppressedError&&SuppressedError;class i{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),this.id=function(e,r=!1){return window.__TAURI_INTERNALS__.transformCallback(e,r)}((e=>{r(this,n,"f").call(this,e)}))}set onmessage(e){t(this,n,e,"f")}get onmessage(){return r(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(e,r={},t){return window.__TAURI_INTERNALS__.invoke(e,r,t)}n=new WeakMap;class o{get rid(){return r(this,s,"f")}constructor(e){s.set(this,void 0),t(this,s,e,"f")}async close(){return a("plugin:resources|close",{rid:this.rid})}}s=new WeakMap;class c extends o{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async downloadAndInstall(e){const r=new i;return e&&(r.onmessage=e),a("plugin:updater|download_and_install",{onEvent:r,rid:this.rid})}}return e.Update=c,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),a("plugin:updater|check",{...e}).then((e=>e.available?new c(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{PendingUpdate, Result, UpdaterExt};
|
||||
use crate::{Result, Update, UpdaterExt};
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::{ipc::Channel, AppHandle, Runtime, State};
|
||||
use tauri::{ipc::Channel, AppHandle, Manager, ResourceId, Runtime};
|
||||
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -30,6 +27,7 @@ pub enum DownloadEvent {
|
||||
#[derive(Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Metadata {
|
||||
rid: Option<ResourceId>,
|
||||
available: bool,
|
||||
current_version: String,
|
||||
version: String,
|
||||
@@ -40,7 +38,6 @@ pub(crate) struct Metadata {
|
||||
#[tauri::command]
|
||||
pub(crate) async fn check<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
pending: State<'_, PendingUpdate>,
|
||||
headers: Option<Vec<(String, String)>>,
|
||||
timeout: Option<u64>,
|
||||
proxy: Option<String>,
|
||||
@@ -72,7 +69,7 @@ pub(crate) async fn check<R: Runtime>(
|
||||
metadata.version = update.version.clone();
|
||||
metadata.date = update.date.map(|d| d.to_string());
|
||||
metadata.body = update.body.clone();
|
||||
pending.0.lock().await.replace(update);
|
||||
metadata.rid = Some(app.resources_table().add(update));
|
||||
}
|
||||
|
||||
Ok(metadata)
|
||||
@@ -80,30 +77,28 @@ pub(crate) async fn check<R: Runtime>(
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn download_and_install<R: Runtime>(
|
||||
_app: AppHandle<R>,
|
||||
pending: State<'_, PendingUpdate>,
|
||||
app: AppHandle<R>,
|
||||
rid: ResourceId,
|
||||
on_event: Channel,
|
||||
) -> Result<()> {
|
||||
if let Some(pending) = &*pending.0.lock().await {
|
||||
let first_chunk = AtomicBool::new(false);
|
||||
let on_event_c = on_event.clone();
|
||||
pending
|
||||
.download_and_install(
|
||||
move |chunk_length, content_length| {
|
||||
if first_chunk.swap(false, Ordering::Acquire) {
|
||||
on_event
|
||||
.send(DownloadEvent::Started { content_length })
|
||||
.unwrap();
|
||||
}
|
||||
on_event
|
||||
.send(DownloadEvent::Progress { chunk_length })
|
||||
.unwrap();
|
||||
},
|
||||
move || {
|
||||
on_event_c.send(&DownloadEvent::Finished).unwrap();
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let update = app.resources_table().get::<Update>(rid)?;
|
||||
|
||||
let mut first_chunk = true;
|
||||
|
||||
update
|
||||
.download_and_install(
|
||||
|chunk_length, content_length| {
|
||||
if first_chunk {
|
||||
first_chunk = !first_chunk;
|
||||
let _ = on_event.send(DownloadEvent::Started { content_length });
|
||||
}
|
||||
let _ = on_event.send(DownloadEvent::Progress { chunk_length });
|
||||
},
|
||||
|| {
|
||||
let _ = on_event.send(&DownloadEvent::Finished);
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ pub enum Error {
|
||||
BinaryNotFoundInArchive,
|
||||
#[error(transparent)]
|
||||
Http(#[from] http::Error),
|
||||
#[error(transparent)]
|
||||
Tauri(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
)]
|
||||
|
||||
use tauri::{
|
||||
async_runtime::Mutex,
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
};
|
||||
@@ -28,8 +27,6 @@ pub use config::Config;
|
||||
pub use error::{Error, Result};
|
||||
pub use updater::*;
|
||||
|
||||
struct PendingUpdate(Mutex<Option<Update>>);
|
||||
|
||||
/// Extension trait to use the updater on [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`].
|
||||
pub trait UpdaterExt<R: Runtime> {
|
||||
/// Gets the updater builder to build and updater
|
||||
@@ -145,7 +142,6 @@ impl Builder {
|
||||
config.installer_args = installer_args;
|
||||
}
|
||||
app.manage(UpdaterState { target, config });
|
||||
app.manage(PendingUpdate(Default::default()));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
|
||||
@@ -20,7 +20,10 @@ use reqwest::{
|
||||
};
|
||||
use semver::Version;
|
||||
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
|
||||
use tauri::utils::{config::UpdaterConfig, platform::current_exe};
|
||||
use tauri::{
|
||||
utils::{config::UpdaterConfig, platform::current_exe},
|
||||
Resource,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
@@ -385,13 +388,15 @@ pub struct Update {
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
impl Resource for Update {}
|
||||
|
||||
impl Update {
|
||||
/// Downloads the updater package, verifies it then return it as bytes.
|
||||
///
|
||||
/// Use [`Update::install`] to install it
|
||||
pub async fn download<C: Fn(usize, Option<u64>), D: FnOnce()>(
|
||||
pub async fn download<C: FnMut(usize, Option<u64>), D: FnOnce()>(
|
||||
&self,
|
||||
on_chunk: C,
|
||||
mut on_chunk: C,
|
||||
on_download_finish: D,
|
||||
) -> Result<Vec<u8>> {
|
||||
// set our headers
|
||||
@@ -458,7 +463,7 @@ impl Update {
|
||||
}
|
||||
|
||||
/// Downloads and installs the updater package
|
||||
pub async fn download_and_install<C: Fn(usize, Option<u64>), D: FnOnce()>(
|
||||
pub async fn download_and_install<C: FnMut(usize, Option<u64>), D: FnOnce()>(
|
||||
&self,
|
||||
on_chunk: C,
|
||||
on_download_finish: D,
|
||||
|
||||
Reference in New Issue
Block a user