diff --git a/package-lock.json b/package-lock.json index e3eb697..ad3a8d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@tauri-apps/plugin-dialog": "^2.4.2", "@tauri-apps/plugin-fs": "^2.4.4", "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-sql": "^2.3.1", "@tauri-apps/plugin-store": "^2.4.1", "@tauri-apps/plugin-updater": "^2.9.0", @@ -1642,6 +1643,15 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-process": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz", + "integrity": "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tauri-apps/plugin-sql": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-sql/-/plugin-sql-2.3.1.tgz", diff --git a/package.json b/package.json index 6b08732..36e0a13 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@tauri-apps/plugin-dialog": "^2.4.2", "@tauri-apps/plugin-fs": "^2.4.4", "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-sql": "^2.3.1", "@tauri-apps/plugin-store": "^2.4.1", "@tauri-apps/plugin-updater": "^2.9.0", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a54aa15..35c0a06 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4825,6 +4825,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-fs", "tauri-plugin-os", + "tauri-plugin-process", "tauri-plugin-sql", "tauri-plugin-store", "tauri-plugin-updater", @@ -5869,6 +5870,16 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "tauri-plugin-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-sql" version = "2.3.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7d1dfa4..01e650e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -36,4 +36,5 @@ image = "0.25.9" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-updater = "2" +tauri-plugin-process = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 5271d66..4d15289 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -22,6 +22,7 @@ "core:path:default", "os:default", "clipboard-manager:allow-write-image", - "clipboard-manager:allow-write-text" + "clipboard-manager:allow-write-text", + "process:allow-restart" ] } \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e10ae1f..0972784 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,11 +1,17 @@ -use tauri_plugin_updater::UpdaterExt; use arboard::Clipboard; use image::ImageReader; +use std::sync::Mutex; +use tauri::{Emitter, Manager}; +use tauri_plugin_updater::UpdaterExt; mod ssh_tunnel; use ssh_tunnel::TunnelManager; +struct PendingUpdate { + bytes: Mutex>>, +} + // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ #[tauri::command] fn greet(name: &str) -> String { @@ -37,12 +43,30 @@ fn copy_image_to_clipboard(path: String) -> Result<(), String> { Ok(()) } +#[tauri::command] +async fn install_update( + app: tauri::AppHandle, + pending: tauri::State<'_, PendingUpdate>, +) -> Result<(), String> { + let bytes = pending.bytes.lock().unwrap().take(); + if let Some(bytes) = bytes { + // Re-check for update to get the Update object needed for install + if let Some(update) = app.updater().map_err(|e| e.to_string())?.check().await.map_err(|e| e.to_string())? { + update.install(&bytes).map_err(|e| e.to_string())?; + app.restart(); + } + } + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_os::init()) .manage(TunnelManager::new()) + .manage(PendingUpdate { bytes: Mutex::new(None) }) .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_sql::Builder::new().build()) .plugin(tauri_plugin_dialog::init()) @@ -51,6 +75,7 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ greet, copy_image_to_clipboard, + install_update, ssh_tunnel::create_ssh_tunnel, ssh_tunnel::close_ssh_tunnel, ssh_tunnel::check_tunnel_status, @@ -59,7 +84,7 @@ pub fn run() { .setup(|app| { let handle = app.handle().clone(); tauri::async_runtime::spawn(async move { - update(handle).await.unwrap(); + let _ = check_for_update(handle).await; }); Ok(()) }) @@ -67,13 +92,13 @@ pub fn run() { .expect("error while running tauri application"); } -async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> { +async fn check_for_update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> { if let Some(update) = app.updater()?.check().await? { let mut downloaded = 0; + let version = update.version.clone(); - // alternatively we could also call update.download() and update.install() separately - update - .download_and_install( + let bytes = update + .download( |chunk_length, content_length| { downloaded += chunk_length; println!("downloaded {downloaded} from {content_length:?}"); @@ -84,8 +109,13 @@ async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> { ) .await?; - println!("update installed"); - app.restart(); + println!("update downloaded, notifying frontend"); + + // Store the bytes for later installation + let pending = app.state::(); + *pending.bytes.lock().unwrap() = Some(bytes); + + let _ = app.emit("update-downloaded", version); } Ok(()) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c95ef39..30129ed 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -8,6 +8,9 @@ import { setShortcuts } from "$lib/shortcuts/index.js"; import KeyboardShortcutsDialog from "$lib/components/keyboard-shortcuts-dialog.svelte"; import CommandPalette from "$lib/components/command-palette.svelte"; + import { listen } from "@tauri-apps/api/event"; + import { invoke } from "@tauri-apps/api/core"; + import { toast } from "svelte-sonner"; setDatabase(); const db = useDatabase(); @@ -18,6 +21,21 @@ function handleBeforeUnload() { db.flushPersistence(); } + + $effect(() => { + const unlisten = listen("update-downloaded", (event) => { + toast.success(`Update v${event.payload} downloaded`, { + action: { + label: "Install & Restart", + onClick: () => invoke("install_update"), + }, + duration: Infinity, + }); + }); + return () => { + unlisten.then((fn) => fn()); + }; + });