From fb790ec1040c044f7d357e77a51598a6b0e2b67b Mon Sep 17 00:00:00 2001
From: unicxrn
Date: Sat, 10 Oct 2026 22:35:31 +0200
Subject: [PATCH] Add updater
---
.github/workflows/release.yml | 8 +
CONTRIBUTING.md | 16 +-
package-lock.json | 27 ++-
package.json | 4 +-
src-tauri/Cargo.lock | 302 +++++++++++++++++++++++++++-
src-tauri/Cargo.toml | 4 +
src-tauri/capabilities/default.json | 4 +-
src-tauri/src/lib.rs | 18 +-
src-tauri/src/settings.rs | 3 +
src-tauri/tauri.conf.json | 16 +-
src/App.svelte | 23 ++-
src/lib/Help.svelte | 20 +-
src/lib/Settings.svelte | 4 +
src/lib/TitleBar.svelte | 17 +-
src/lib/UpdateDialog.svelte | 85 ++++++++
src/lib/api.js | 11 +-
src/lib/mock.js | 22 +-
src/lib/updates.svelte.js | 88 ++++++++
18 files changed, 654 insertions(+), 18 deletions(-)
create mode 100644 src/lib/UpdateDialog.svelte
create mode 100644 src/lib/updates.svelte.js
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6d95b6e..ceab841 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,8 @@ jobs:
build:
strategy:
fail-fast: false
+ # One at a time, so the two jobs don't race to write latest.json (the updater feed).
+ max-parallel: 1
matrix:
os: [ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.os }}
@@ -65,7 +67,13 @@ jobs:
- name: Build and draft the release
uses: tauri-apps/tauri-action@v0
+ env:
+ # Signs the update files. The public half is in src-tauri/tauri.conf.json.
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
with:
+ includeUpdaterJson: true
+ updaterJsonPreferNsis: true
tagName: ${{ github.ref_name }}
releaseName: Prospack ${{ github.ref_name }}
releaseBody: |
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 75128ff..8551fa3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -68,9 +68,23 @@ The ShadowMountPlus details:
- It ignores dot-files, which is why uploads use `..ffpfsc.prospack-part`.
- It only reliably notices a new image when `manual.lst` changes.
+## Updates and signing
+
+Prospack updates itself with Tauri's updater. It reads `latest.json` from the latest
+published GitHub release and only installs files signed with the project's private key.
+The public key is in `src-tauri/tauri.conf.json`.
+
+- `npm run tauri dev` and `npx tauri build --no-bundle` don't need the key.
+- A full `npx tauri build` makes update files, so it needs the key in the environment:
+ `TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/prospack.key)"`. If you don't have the key,
+ add `--config '{"bundle":{"createUpdaterArtifacts":false}}'` to build without update files.
+- The release workflow reads the key from the `TAURI_SIGNING_PRIVATE_KEY` repository secret.
+- A `.deb` install can't replace itself. Those users are pointed at the release page instead.
+
## Releases
-Push a tag such as `v0.1.0`. `.github/workflows/release.yml` builds Linux and Windows,
+Bump the version in `package.json`, `src-tauri/Cargo.toml` and `src-tauri/tauri.conf.json`,
+then push a tag such as `v0.1.0`. `.github/workflows/release.yml` builds Linux and Windows,
runs the tests (including the end-to-end tests with the frozen engine), builds Prospack
Setup, smoke-tests an unattended install and uninstall on Windows, and creates a **draft**
release for you to check and publish.
diff --git a/package-lock.json b/package-lock.json
index 33d1d10..6b6eeef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,18 +1,21 @@
{
- "name": "ps5-packer",
+ "name": "prospack",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "ps5-packer",
+ "name": "prospack",
"version": "0.1.0",
+ "license": "GPL-3.0-only",
"dependencies": {
"@fontsource-variable/geist": "^5.3.0",
"@fontsource-variable/geist-mono": "^5.3.0",
"@tauri-apps/api": "^2.12.2",
"@tauri-apps/plugin-dialog": "^2.8.1",
- "@tauri-apps/plugin-opener": "^2.7.0"
+ "@tauri-apps/plugin-opener": "^2.7.0",
+ "@tauri-apps/plugin-process": "^2.4.0",
+ "@tauri-apps/plugin-updater": "^2.13.3"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^7.3.1",
@@ -669,6 +672,24 @@
"@tauri-apps/api": "^2.12.0"
}
},
+ "node_modules/@tauri-apps/plugin-process": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.4.0.tgz",
+ "integrity": "sha512-GddrmsSLAYrLa/yTXlXK/YRDzUgYeeOnUXTbiyvJWkmI424RmDPD3FVLeqcon7rv6O6ZfhBilcrHWnSo7o6hIQ==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@tauri-apps/api": "^2.12.0"
+ }
+ },
+ "node_modules/@tauri-apps/plugin-updater": {
+ "version": "2.13.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.13.3.tgz",
+ "integrity": "sha512-CtbA8CZ79WJry2HbRiFGx6WZN7uspho6A4jM+TxmuqwWL2EEfJEMw6BIYgRncIZyqp4vEXw3U/97ZjQ33dKUgg==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@tauri-apps/api": "^2.12.0"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
diff --git a/package.json b/package.json
index 2d42f19..529f84a 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,9 @@
"@fontsource-variable/geist-mono": "^5.3.0",
"@tauri-apps/api": "^2.12.2",
"@tauri-apps/plugin-dialog": "^2.8.1",
- "@tauri-apps/plugin-opener": "^2.7.0"
+ "@tauri-apps/plugin-opener": "^2.7.0",
+ "@tauri-apps/plugin-process": "^2.4.0",
+ "@tauri-apps/plugin-updater": "^2.13.3"
},
"license": "GPL-3.0-only"
}
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index e12eddc..5bb00d2 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -47,6 +47,15 @@ version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+[[package]]
+name = "arbitrary"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
+dependencies = [
+ "derive_arbitrary",
+]
+
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -532,7 +541,7 @@ dependencies = [
"bitflags 2.13.2",
"core-foundation",
"core-graphics-types",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
]
@@ -704,6 +713,17 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "derive_arbitrary"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.7",
+]
+
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -972,6 +992,16 @@ dependencies = [
"rustc_version",
]
+[[package]]
+name = "filetime"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
+dependencies = [
+ "cfg-if",
+ "libc",
+]
+
[[package]]
name = "find-msvc-tools"
version = "0.1.14"
@@ -1001,6 +1031,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
[[package]]
name = "foreign-types"
version = "0.5.0"
@@ -1008,7 +1047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
- "foreign-types-shared",
+ "foreign-types-shared 0.3.1",
]
[[package]]
@@ -1022,6 +1061,12 @@ dependencies = [
"syn 3.0.7",
]
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
@@ -1515,6 +1560,22 @@ dependencies = [
"want",
]
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
[[package]]
name = "hyper-util"
version = "0.1.21"
@@ -2052,6 +2113,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+[[package]]
+name = "minisign-verify"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce"
+
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -2114,6 +2181,23 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "native-tls"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
[[package]]
name = "ndk"
version = "0.9.0"
@@ -2327,6 +2411,18 @@ dependencies = [
"objc2-core-foundation",
]
+[[package]]
+name = "objc2-osa-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+]
+
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@@ -2401,6 +2497,49 @@ dependencies = [
"libc",
]
+[[package]]
+name = "openssl"
+version = "0.10.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
+dependencies = [
+ "bitflags 2.13.2",
+ "cfg-if",
+ "foreign-types 0.3.2",
+ "libc",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -2417,6 +2556,20 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "osakit"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+ "objc2-osa-kit",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
[[package]]
name = "pango"
version = "0.18.3"
@@ -2719,6 +2872,8 @@ dependencies = [
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-opener",
+ "tauri-plugin-process",
+ "tauri-plugin-updater",
"tempfile",
]
@@ -2846,15 +3001,19 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
+ "hyper-tls",
"hyper-util",
"js-sys",
"log",
+ "native-tls",
"percent-encoding",
"pin-project-lite",
+ "rustls-pki-types",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
+ "tokio-native-tls",
"tokio-util",
"tower",
"tower-http",
@@ -2918,6 +3077,15 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
+dependencies = [
+ "zeroize",
+]
+
[[package]]
name = "rustversion"
version = "1.0.23"
@@ -2933,6 +3101,15 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "schannel"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "schemars"
version = "0.8.22"
@@ -2990,6 +3167,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "security-framework"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
+dependencies = [
+ "bitflags 2.13.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "selectors"
version = "0.38.0"
@@ -3450,6 +3650,17 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "tar"
+version = "0.4.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
[[package]]
name = "target-lexicon"
version = "0.12.16"
@@ -3649,6 +3860,49 @@ dependencies = [
"zbus",
]
+[[package]]
+name = "tauri-plugin-process"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74ae3d8549ae564c0397377373e262f340908eb23a5b9fbe03f2a00007dc0038"
+dependencies = [
+ "tauri",
+ "tauri-plugin",
+]
+
+[[package]]
+name = "tauri-plugin-updater"
+version = "2.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "855a336eb389d2c1502244fd48fa2d8cb487a430f691bbaed025195f99a04489"
+dependencies = [
+ "base64 0.22.1",
+ "dirs",
+ "flate2",
+ "futures-util",
+ "http",
+ "infer",
+ "libc",
+ "log",
+ "minisign-verify",
+ "osakit",
+ "percent-encoding",
+ "reqwest",
+ "semver",
+ "serde",
+ "serde_json",
+ "tar",
+ "tauri",
+ "tauri-plugin",
+ "tempfile",
+ "thiserror 2.0.21",
+ "time",
+ "tokio",
+ "url",
+ "windows-sys 0.61.2",
+ "zip",
+]
+
[[package]]
name = "tauri-runtime"
version = "2.12.1"
@@ -3871,6 +4125,16 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
[[package]]
name = "tokio-util"
version = "0.7.20"
@@ -4162,6 +4426,12 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
[[package]]
name = "version-compare"
version = "0.2.1"
@@ -4827,6 +5097,16 @@ dependencies = [
"pkg-config",
]
+[[package]]
+name = "xattr"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
[[package]]
name = "yoke"
version = "0.8.3"
@@ -4941,6 +5221,12 @@ dependencies = [
"synstructure",
]
+[[package]]
+name = "zeroize"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
+
[[package]]
name = "zerotrie"
version = "0.2.5"
@@ -4974,6 +5260,18 @@ dependencies = [
"syn 3.0.7",
]
+[[package]]
+name = "zip"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "indexmap 2.14.2",
+ "memchr",
+]
+
[[package]]
name = "zlib-rs"
version = "0.6.8"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 294472b..129d7d1 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -17,6 +17,7 @@ tauri-build = { version = "2", features = [] }
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2"
tauri-plugin-opener = "2"
+tauri-plugin-process = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
@@ -24,6 +25,9 @@ base64 = "0.22"
fs4 = "1"
if-addrs = "0.15"
+[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
+tauri-plugin-updater = { version = "2", default-features = false, features = ["native-tls", "zip"] }
+
[target.'cfg(unix)'.dependencies]
libc = "0.2"
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index a307826..424a797 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -14,6 +14,8 @@
"dialog:default",
"opener:default",
"opener:allow-reveal-item-in-dir",
- "opener:allow-open-url"
+ "opener:allow-open-url",
+ "updater:default",
+ "process:allow-restart"
]
}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index ca6902e..5ce2e51 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -118,6 +118,19 @@ fn free_space(path: String) -> Option {
pipeline::free_space(&p)
}
+/// How this copy was installed, which decides whether it can update itself:
+/// "windows" and "appimage" can; "system" (a .deb or a source build) can't.
+#[tauri::command]
+fn install_kind() -> &'static str {
+ if cfg!(windows) {
+ "windows"
+ } else if std::env::var_os("APPIMAGE").is_some() {
+ "appimage"
+ } else {
+ "system"
+ }
+}
+
#[tauri::command]
async fn discover_ps5() -> Result, String> {
blocking(discover::discover).await
@@ -128,6 +141,8 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
+ .plugin(tauri_plugin_process::init())
+ .plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| {
paths::init(app.path().app_config_dir()?, app.path().app_data_dir()?);
settings::migrate_legacy();
@@ -163,7 +178,8 @@ pub fn run() {
resume_job,
ps5_status,
discover_ps5,
- free_space
+ free_space,
+ install_kind
])
.run(tauri::generate_context!())
.expect("error while running Prospack");
diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs
index dfa9fa7..6b9e129 100644
--- a/src-tauri/src/settings.rs
+++ b/src-tauri/src/settings.rs
@@ -27,6 +27,8 @@ pub struct Settings {
pub delete_local_after_upload: bool,
/// The first-run wizard has been completed (or skipped).
pub setup_done: bool,
+ /// Look for a new Prospack release at startup and every few hours.
+ pub check_updates: bool,
}
impl Default for Settings {
@@ -45,6 +47,7 @@ impl Default for Settings {
mark_gc_validated: true,
delete_local_after_upload: false,
setup_done: false,
+ check_updates: true,
}
}
}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 2945ae4..09a846d 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -61,7 +61,19 @@
"depends": []
}
},
- "category": "Utility"
+ "category": "Utility",
+ "createUpdaterArtifacts": true
},
- "mainBinaryName": "prospack"
+ "mainBinaryName": "prospack",
+ "plugins": {
+ "updater": {
+ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU0OUJGQkQ3MUQ5QTZGREIKUldUYmI1b2QxL3ViNU1TY1NXUnh6YkhZQXpPczgyd1hTUHlWdlpHN2JJeGNDdmRZZThrU3FzR0sK",
+ "endpoints": [
+ "https://github.com/unicxrn/prospack/releases/latest/download/latest.json"
+ ],
+ "windows": {
+ "installMode": "passive"
+ }
+ }
+ }
}
diff --git a/src/App.svelte b/src/App.svelte
index f279cbf..d8a56f7 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -12,6 +12,8 @@
import Settings from "./lib/Settings.svelte";
import Setup from "./lib/Setup.svelte";
import Help from "./lib/Help.svelte";
+ import UpdateDialog from "./lib/UpdateDialog.svelte";
+ import { updates, startAutoCheck, installUpdate } from "./lib/updates.svelte.js";
let jobs = $state([]);
let selected = $state(null);
@@ -21,6 +23,9 @@
let ps5 = $state(null);
let showSettings = $state(false);
let showHelp = $state(false);
+ let showUpdate = $state(false);
+ // Set when the user chose "Update when it's done" while a game was running.
+ let updateWhenIdle = $state(false);
let dragging = $state(false);
let installing = $state(false);
let installLog = $state([]);
@@ -84,6 +89,15 @@
}
const inSetup = $derived(settings && !settings.setupDone);
+ const runningGame = $derived(jobs.find((j) => j.status === "running" || j.status === "queued")?.dump.titleName ?? null);
+
+ $effect(() => {
+ if (updateWhenIdle && !runningGame && updates.available?.canInstall && !updates.installing) {
+ updateWhenIdle = false;
+ showUpdate = true;
+ installUpdate();
+ }
+ });
async function setupDone(next, pickNow) {
try {
@@ -132,6 +146,7 @@
})
);
refreshPs5();
+ offs.push(startAutoCheck(() => settings?.checkUpdates !== false));
})();
// A running upload already proves the link; poll less to keep FTP free.
const t = setInterval(() => !jobs.some((j) => j.status === "running" && j.stage >= 2) && refreshPs5(), 20000);
@@ -157,7 +172,7 @@
- (showSettings = true)} onHelp={() => (showHelp = true)} />
+ (showSettings = true)} onHelp={() => (showHelp = true)} onUpdate={() => (showUpdate = true)} />
{#if jobs.length && !inSetup}
(selected = id)} onAdd={pick} />
@@ -212,8 +227,12 @@
(showSettings = false)} onSaved={(s) => { settings = s; refreshPs5(); }} onRerunSetup={rerunSetup} />
{/if}
+ {#if showUpdate && updates.available}
+ (updateWhenIdle = true)} onClose={() => (showUpdate = false)} />
+ {/if}
+
{#if showHelp}
- (showHelp = false)} onRerunSetup={rerunSetup} />
+ (showHelp = false)} onRerunSetup={rerunSetup} onShowUpdate={() => { showHelp = false; showUpdate = true; }} />
{/if}
diff --git a/src/lib/Help.svelte b/src/lib/Help.svelte
index 4c1da59..0066508 100644
--- a/src/lib/Help.svelte
+++ b/src/lib/Help.svelte
@@ -2,8 +2,16 @@
import { onMount } from "svelte";
import { fade, fly } from "svelte/transition";
import { api, isTauri } from "./api.js";
+ import { updates, checkForUpdates } from "./updates.svelte.js";
- let { onClose, onRerunSetup } = $props();
+ let { onClose, onRerunSetup, onShowUpdate } = $props();
+ let checkedHere = $state(false);
+
+ async function check() {
+ await checkForUpdates();
+ checkedHere = true;
+ if (updates.available) onShowUpdate();
+ }
let version = $state("dev");
const REPO = "https://github.com/unicxrn/prospack";
@@ -128,6 +136,14 @@
and
. Thanks to their authors.
+
+
+ {#if checkedHere && !updates.checking}
+
+ {#if updates.lastError}Couldn't check: {updates.lastError}{:else if updates.available}Prospack {updates.available.version} is available.{:else}You're on the latest version.{/if}
+
+ {/if}
+
Only use dumps of games you own.
@@ -163,4 +179,6 @@
.link { background: none; border: 0; color: var(--text); text-decoration: underline; text-decoration-color: var(--line-strong); cursor: pointer; padding: 0; font: inherit; }
.link:hover { text-decoration-color: currentColor; }
.credits { font-size: 12.5px; }
+ .upd { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: 12.5px; }
+ .upd .err { color: var(--err); }
diff --git a/src/lib/Settings.svelte b/src/lib/Settings.svelte
index 367b8d5..5560ffe 100644
--- a/src/lib/Settings.svelte
+++ b/src/lib/Settings.svelte
@@ -106,6 +106,10 @@
Show as validated in Game CompressorWrites Game Compressor's validation record after Prospack's own file-by-file check, with the original size and space saved.
+