From 0879a87a7ecc83c9e886e6f1412fe253082b8d34 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 18 Jan 2024 13:48:44 +0200 Subject: [PATCH] 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 --- .changes/updater-js-started-event.md | 6 +++ .changes/updater-on-chunk-fn-mut.md | 5 +++ plugins/updater/guest-js/index.ts | 15 +++++--- plugins/updater/src/api-iife.js | 2 +- plugins/updater/src/commands.rs | 57 +++++++++++++--------------- plugins/updater/src/error.rs | 2 + plugins/updater/src/lib.rs | 4 -- plugins/updater/src/updater.rs | 13 +++++-- 8 files changed, 58 insertions(+), 46 deletions(-) create mode 100644 .changes/updater-js-started-event.md create mode 100644 .changes/updater-on-chunk-fn-mut.md diff --git a/.changes/updater-js-started-event.md b/.changes/updater-js-started-event.md new file mode 100644 index 00000000..61fdcb03 --- /dev/null +++ b/.changes/updater-js-started-event.md @@ -0,0 +1,6 @@ +--- +"updater": "patch" +"updater-js": "patch" +--- + +Fix `Started` event not emitted to JS when downloading update. diff --git a/.changes/updater-on-chunk-fn-mut.md b/.changes/updater-on-chunk-fn-mut.md new file mode 100644 index 00000000..70631e13 --- /dev/null +++ b/.changes/updater-on-chunk-fn-mut.md @@ -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`. diff --git a/plugins/updater/guest-js/index.ts b/plugins/updater/guest-js/index.ts index 411c989f..e3f8ec29 100644 --- a/plugins/updater/guest-js/index.ts +++ b/plugins/updater/guest-js/index.ts @@ -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 { const channel = new Channel(); - 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 { options.headers = Array.from(new Headers(options.headers).entries()); } - return invoke("plugin:updater|check", { ...options }).then( - (meta) => (meta.available ? new Update(meta) : null), - ); + return invoke("plugin:updater|check", { + ...options, + }).then((meta) => (meta.available ? new Update(meta) : null)); } export type { CheckOptions, DownloadEvent }; diff --git a/plugins/updater/src/api-iife.js b/plugins/updater/src/api-iife.js index df1870a2..2e443929 100644 --- a/plugins/updater/src/api-iife.js +++ b/plugins/updater/src/api-iife.js @@ -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__})} diff --git a/plugins/updater/src/commands.rs b/plugins/updater/src/commands.rs index 24abc3de..6d2668f2 100644 --- a/plugins/updater/src/commands.rs +++ b/plugins/updater/src/commands.rs @@ -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, available: bool, current_version: String, version: String, @@ -40,7 +38,6 @@ pub(crate) struct Metadata { #[tauri::command] pub(crate) async fn check( app: AppHandle, - pending: State<'_, PendingUpdate>, headers: Option>, timeout: Option, proxy: Option, @@ -72,7 +69,7 @@ pub(crate) async fn check( 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( #[tauri::command] pub(crate) async fn download_and_install( - _app: AppHandle, - pending: State<'_, PendingUpdate>, + app: AppHandle, + 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::(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(()) } diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs index a095eb18..3b927b74 100644 --- a/plugins/updater/src/error.rs +++ b/plugins/updater/src/error.rs @@ -66,6 +66,8 @@ pub enum Error { BinaryNotFoundInArchive, #[error(transparent)] Http(#[from] http::Error), + #[error(transparent)] + Tauri(#[from] tauri::Error), } impl Serialize for Error { diff --git a/plugins/updater/src/lib.rs b/plugins/updater/src/lib.rs index 0475620d..71cf6e78 100644 --- a/plugins/updater/src/lib.rs +++ b/plugins/updater/src/lib.rs @@ -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>); - /// Extension trait to use the updater on [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`]. pub trait UpdaterExt { /// 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![ diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs index 5c804504..59e0383a 100644 --- a/plugins/updater/src/updater.rs +++ b/plugins/updater/src/updater.rs @@ -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), D: FnOnce()>( + pub async fn download), D: FnOnce()>( &self, - on_chunk: C, + mut on_chunk: C, on_download_finish: D, ) -> Result> { // set our headers @@ -458,7 +463,7 @@ impl Update { } /// Downloads and installs the updater package - pub async fn download_and_install), D: FnOnce()>( + pub async fn download_and_install), D: FnOnce()>( &self, on_chunk: C, on_download_finish: D,