mirror of
https://github.com/unicxrn/prospack.git
synced 2026-10-28 17:26:07 +01:00
Add updater
This commit is contained in:
1 parent
65db70c9d2
commit
fb790ec104
18 files changed
+654
-18
No files matched your search
@@ -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: |
|
||||
|
||||
+15
-1
@@ -68,9 +68,23 @@ The ShadowMountPlus details:
|
||||
- It ignores dot-files, which is why uploads use `.<TITLE>.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.
|
||||
Generated
+24
-3
@@ -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",
|
||||
|
||||
+3
-1
@@ -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"
|
||||
}
|
||||
Generated
+300
-2
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
+17
-1
@@ -118,6 +118,19 @@ fn free_space(path: String) -> Option<u64> {
|
||||
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<Vec<discover::Found>, 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");
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-2
@@ -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 @@
|
||||
</script>
|
||||
|
||||
<div class="app" class:empty={!jobs.length || inSetup} style:--accent={job?.dump.accent ?? "#9b7bff"}>
|
||||
<TitleBar {ps5} host={settings?.ps5Host} onSettings={() => (showSettings = true)} onHelp={() => (showHelp = true)} />
|
||||
<TitleBar {ps5} host={settings?.ps5Host} onSettings={() => (showSettings = true)} onHelp={() => (showHelp = true)} onUpdate={() => (showUpdate = true)} />
|
||||
|
||||
{#if jobs.length && !inSetup}
|
||||
<Queue {jobs} {selected} {art} {ps5} onSelect={(id) => (selected = id)} onAdd={pick} />
|
||||
@@ -212,8 +227,12 @@
|
||||
<Settings {settings} onClose={() => (showSettings = false)} onSaved={(s) => { settings = s; refreshPs5(); }} onRerunSetup={rerunSetup} />
|
||||
{/if}
|
||||
|
||||
{#if showUpdate && updates.available}
|
||||
<UpdateDialog busyGame={runningGame} pending={updateWhenIdle} onPending={() => (updateWhenIdle = true)} onClose={() => (showUpdate = false)} />
|
||||
{/if}
|
||||
|
||||
{#if showHelp}
|
||||
<Help onClose={() => (showHelp = false)} onRerunSetup={rerunSetup} />
|
||||
<Help onClose={() => (showHelp = false)} onRerunSetup={rerunSetup} onShowUpdate={() => { showHelp = false; showUpdate = true; }} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
+19
-1
@@ -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 @@
|
||||
<button class="link" onclick={() => open("https://github.com/juma-sayeh/PS5-Game-Compressor")}>Game Compressor</button> and
|
||||
<button class="link" onclick={() => open("https://github.com/ps5-payload-dev/ftpsrv")}>ftpsrv</button>. Thanks to their authors.
|
||||
</p>
|
||||
<div class="upd">
|
||||
<button class="btn" onclick={check} disabled={updates.checking}>{updates.checking ? "Checking…" : "Check for updates"}</button>
|
||||
{#if checkedHere && !updates.checking}
|
||||
<span class:err={updates.lastError}>
|
||||
{#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}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="credits">Only use dumps of games you own.</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -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); }
|
||||
</style>
|
||||
@@ -106,6 +106,10 @@
|
||||
<input type="checkbox" bind:checked={draft.markGcValidated} /><span class="sw"></span>
|
||||
<span><b>Show as validated in Game Compressor</b><small>Writes Game Compressor's validation record after Prospack's own file-by-file check, with the original size and space saved.</small></span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" bind:checked={draft.checkUpdates} /><span class="sw"></span>
|
||||
<span><b>Check for updates</b><small>Looks for a new Prospack release when it starts and every six hours. Nothing installs without you clicking it.</small></span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" bind:checked={draft.deleteLocalAfterUpload} /><span class="sw"></span>
|
||||
<span><b>Delete the local image after it mounts</b><small>Only once ShadowMountPlus has confirmed the mount.</small></span>
|
||||
|
||||
+16
-1
@@ -1,8 +1,9 @@
|
||||
<script>
|
||||
import { api, isTauri } from "./api.js";
|
||||
import { gb } from "./format.js";
|
||||
import { updates } from "./updates.svelte.js";
|
||||
|
||||
let { ps5, host, onSettings, onHelp } = $props();
|
||||
let { ps5, host, onSettings, onHelp, onUpdate } = $props();
|
||||
</script>
|
||||
|
||||
<header class="bar" data-tauri-drag-region>
|
||||
@@ -29,6 +30,12 @@
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if updates.available}
|
||||
<button class="update" onclick={onUpdate} title="Prospack {updates.available.version} is available">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M6 1.5v6.5M3.2 5.4 6 8.2l2.8-2.8M2 10.5h8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Update to {updates.available.version}
|
||||
</button>
|
||||
{/if}
|
||||
<button class="iconbtn" aria-label="Help" title="Help (F1)" onclick={onHelp}>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.3" stroke="currentColor" stroke-width="1.3"/><path d="M6.2 6.3a1.9 1.9 0 0 1 3.7.4c0 1.2-1.9 1.5-1.9 2.6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="8" cy="11.4" r=".75" fill="currentColor"/></svg>
|
||||
</button>
|
||||
@@ -74,6 +81,14 @@
|
||||
.unset .dot::after { display: none; }
|
||||
.off .dot::after { display: none; }
|
||||
@keyframes ping { 0% { transform: scale(0.4); opacity: 0.7; } 80%, 100% { transform: scale(1.4); opacity: 0; } }
|
||||
.update {
|
||||
display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-radius: 99px; cursor: pointer;
|
||||
border: 1px solid color-mix(in srgb, #9b7bff 45%, transparent); background: color-mix(in srgb, #9b7bff 14%, transparent);
|
||||
color: #d9ccff; font-size: 12px; font-weight: 500; animation: arrive 0.5s var(--ease);
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.update:hover { background: color-mix(in srgb, #9b7bff 24%, transparent); }
|
||||
@keyframes arrive { from { opacity: 0; transform: translateY(-4px); } }
|
||||
.sep { width: 1px; height: 14px; background: var(--line-strong); }
|
||||
.win { display: flex; gap: 2px; margin-left: 6px; padding-left: 10px; border-left: 1px solid var(--line); }
|
||||
.close:hover { background: var(--err); color: #fff; }
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<script>
|
||||
import { fade, scale } from "svelte/transition";
|
||||
import { api } from "./api.js";
|
||||
import { updates, installUpdate, releasePage } from "./updates.svelte.js";
|
||||
|
||||
let { busyGame, pending, onPending, onClose } = $props();
|
||||
|
||||
const a = $derived(updates.available);
|
||||
const pct = $derived(updates.progress?.total ? (updates.progress.got / updates.progress.total) * 100 : 0);
|
||||
const mb = (b) => (b / 1e6).toFixed(1);
|
||||
|
||||
function onKey(e) {
|
||||
if (e.key === "Escape" && !updates.installing) onClose();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onKey} />
|
||||
|
||||
<div class="scrim" transition:fade={{ duration: 180 }} onclick={() => !updates.installing && onClose()} role="presentation"></div>
|
||||
<div class="dialog" role="dialog" aria-label="Update Prospack" transition:scale={{ start: 0.96, duration: 260, opacity: 0 }}>
|
||||
{#if a}
|
||||
<div class="kicker">Update available</div>
|
||||
<h2>Prospack {a.version}</h2>
|
||||
<p class="sub">You have {updates.current}.</p>
|
||||
|
||||
{#if a.notes.trim()}
|
||||
<div class="notes">
|
||||
{#each a.notes.split("\n").map((l) => l.replace(/^[-*]\s*/, "").trim()).filter(Boolean) as line}
|
||||
<p>{line}</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if updates.installing}
|
||||
<div class="bar"><i style:width="{pct}%"></i></div>
|
||||
<p class="sub mono">
|
||||
{#if updates.progress?.total && updates.progress.got < updates.progress.total}
|
||||
Downloading {mb(updates.progress.got)} of {mb(updates.progress.total)} MB
|
||||
{:else}
|
||||
Installing. Prospack will restart in a moment.
|
||||
{/if}
|
||||
</p>
|
||||
{:else if updates.lastError}
|
||||
<p class="err">The update didn't install: {updates.lastError}</p>
|
||||
{/if}
|
||||
|
||||
<footer>
|
||||
{#if !a.canInstall}
|
||||
<span class="hint">This copy was installed from a .deb, so it can't update itself.</span>
|
||||
<button class="btn ghost" onclick={onClose}>Later</button>
|
||||
<button class="btn primary" onclick={() => api.openUrl(releasePage(a.version))}>Download {a.version}</button>
|
||||
{:else if updates.installing}
|
||||
<span class="hint">Keep Prospack open until it restarts.</span>
|
||||
{:else if busyGame}
|
||||
<span class="hint">{busyGame} is still running. The update waits until it's done.</span>
|
||||
<button class="btn ghost" onclick={onClose}>Later</button>
|
||||
<button class="btn primary" disabled={pending} onclick={onPending}>{pending ? "Waiting for the game…" : "Update when it's done"}</button>
|
||||
{:else}
|
||||
<button class="btn ghost" onclick={() => api.openUrl(releasePage(a.version))}>Release notes</button>
|
||||
<button class="btn ghost" onclick={onClose}>Later</button>
|
||||
<button class="btn primary" onclick={installUpdate}>Update and restart</button>
|
||||
{/if}
|
||||
</footer>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scrim { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 60; backdrop-filter: blur(2px); }
|
||||
.dialog {
|
||||
position: fixed; z-index: 61; left: 50%; top: 50%; translate: -50% -50%; width: min(460px, calc(100vw - 32px));
|
||||
background: var(--surface); border: 1px solid var(--line-strong); border-radius: 16px; padding: 22px 24px 18px;
|
||||
box-shadow: 0 40px 80px rgba(0, 0, 0, 0.45); user-select: text;
|
||||
}
|
||||
.kicker { color: var(--accent); }
|
||||
h2 { font-weight: 500; font-size: 24px; letter-spacing: -0.03em; margin: 8px 0 2px; }
|
||||
.sub { color: var(--muted); font-size: 12.5px; }
|
||||
.notes { margin-top: 14px; border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; max-height: 180px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; color: var(--muted); font-size: 12.5px; }
|
||||
.notes p { padding-left: 14px; position: relative; }
|
||||
.notes p::before { content: ""; position: absolute; left: 2px; top: 8px; width: 4px; height: 4px; border-radius: 50%; background: var(--faint); }
|
||||
.bar { height: 5px; border-radius: 5px; background: var(--line); margin: 18px 0 8px; overflow: hidden; }
|
||||
.bar i { display: block; height: 100%; background: var(--accent); transition: width 0.2s var(--ease); }
|
||||
.err { color: var(--err); font-size: 12.5px; margin-top: 12px; }
|
||||
footer { display: flex; justify-content: flex-end; align-items: center; gap: 8px; margin-top: 20px; flex-wrap: wrap; }
|
||||
.hint { margin-right: auto; color: var(--faint); font-size: 12px; max-width: 26ch; }
|
||||
</style>
|
||||
+9
-2
@@ -6,15 +6,17 @@ export const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in
|
||||
let real = null;
|
||||
async function tauri() {
|
||||
if (!real) {
|
||||
const [core, event, dialog, opener, win, webview] = await Promise.all([
|
||||
const [core, event, dialog, opener, win, webview, updater, process] = await Promise.all([
|
||||
import("@tauri-apps/api/core"),
|
||||
import("@tauri-apps/api/event"),
|
||||
import("@tauri-apps/plugin-dialog"),
|
||||
import("@tauri-apps/plugin-opener"),
|
||||
import("@tauri-apps/api/window"),
|
||||
import("@tauri-apps/api/webview"),
|
||||
import("@tauri-apps/plugin-updater"),
|
||||
import("@tauri-apps/plugin-process"),
|
||||
]);
|
||||
real = { core, event, dialog, opener, win, webview };
|
||||
real = { core, event, dialog, opener, win, webview, updater, process };
|
||||
}
|
||||
return real;
|
||||
}
|
||||
@@ -37,6 +39,11 @@ export const api = isTauri
|
||||
ps5Status: (value) => call("ps5_status", { value: value ?? null }),
|
||||
discoverPs5: () => call("discover_ps5"),
|
||||
freeSpace: (path) => call("free_space", { path }),
|
||||
installKind: () => call("install_kind"),
|
||||
appVersion: async () => (await import("@tauri-apps/api/app")).getVersion(),
|
||||
// Returns the updater's Update object (or null). Throws if the update feed can't be read.
|
||||
checkUpdate: async () => (await tauri()).updater.check(),
|
||||
relaunch: async () => (await tauri()).process.relaunch(),
|
||||
on: async (name, fn) => (await tauri()).event.listen(name, (e) => fn(e.payload)),
|
||||
pickFolder: async (title) => (await tauri()).dialog.open({ directory: true, multiple: false, title }),
|
||||
reveal: async (path) => (await tauri()).opener.revealItemInDir(path),
|
||||
|
||||
+21
-1
@@ -9,7 +9,7 @@ let settings = {
|
||||
setupDone: new URLSearchParams(location.search).has("setup") ? false : true,
|
||||
ps5Host: "192.168.0.50", ftpPort: 2121, webPort: 8888, remoteDir: "/data/homebrew",
|
||||
outputDir: "/home/you/PS5 Images", compressionLevel: 7, cpuCount: 0, verifyWorkers: 0,
|
||||
autoUpload: true, writeSmpHints: true, markGcValidated: true, deleteLocalAfterUpload: false,
|
||||
autoUpload: true, writeSmpHints: true, markGcValidated: true, checkUpdates: true, deleteLocalAfterUpload: false,
|
||||
};
|
||||
|
||||
const sample = {
|
||||
@@ -125,6 +125,26 @@ export const mock = {
|
||||
resumeJob: async (id) => { const j = jobs.find((x) => x.id === id); if (j) { j.status = "running"; j.stages[j.stage].state = "live"; } emit("jobs", structuredClone(jobs)); },
|
||||
discoverPs5: async () => { await delay(1600); return [{ ip: "192.168.0.50", port: 2121, label: "ftpsrv v1.6.22", banner: "" }]; },
|
||||
freeSpace: async () => 529_000_000_000,
|
||||
installKind: async () => new URLSearchParams(location.search).get("kind") ?? "appimage",
|
||||
appVersion: async () => "0.1.0",
|
||||
checkUpdate: async () => {
|
||||
await delay(700);
|
||||
if (!new URLSearchParams(location.search).has("update")) return null;
|
||||
return {
|
||||
version: "0.2.0",
|
||||
body: "Faster verify on large dumps.\nThe wizard remembers manual IPs.\nFixes a crash when the image folder was on a removed drive.",
|
||||
downloadAndInstall: async (onEvent) => {
|
||||
const total = 42_000_000;
|
||||
onEvent({ event: "Started", data: { contentLength: total } });
|
||||
for (let got = 0; got < total; got += 1_400_000) {
|
||||
await delay(60);
|
||||
onEvent({ event: "Progress", data: { chunkLength: 1_400_000 } });
|
||||
}
|
||||
onEvent({ event: "Finished" });
|
||||
},
|
||||
};
|
||||
},
|
||||
relaunch: async () => location.reload(),
|
||||
ps5Status: async () => ({ online: true, latencyMs: 4, error: null, freeBytes: 631_629_479_936, totalBytes: 937_046_507_520, shadowmount: true, gameCompressor: true }),
|
||||
on: async (name, fn) => { (listeners[name] ||= []).push(fn); return () => {}; },
|
||||
pickFolder: async () => null,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Update state shared by the title bar, the update dialog, Help and Settings.
|
||||
import { api } from "./api.js";
|
||||
|
||||
const REPO = "unicxrn/prospack";
|
||||
const SIX_HOURS = 6 * 60 * 60 * 1000;
|
||||
|
||||
export const updates = $state({
|
||||
current: null,
|
||||
kind: null, // "windows" | "appimage" | "system"
|
||||
available: null, // { version, notes, canInstall }
|
||||
checking: false,
|
||||
lastError: null,
|
||||
lastChecked: null,
|
||||
// While downloading or installing: { got, total }
|
||||
progress: null,
|
||||
installing: false,
|
||||
});
|
||||
|
||||
let handle = null; // the updater's Update object, needed to install
|
||||
|
||||
/** "0.10.2" > "0.9.9", ignoring a leading "v" and any "-suffix". */
|
||||
export function newer(a, b) {
|
||||
const parts = (v) => String(v).replace(/^v/, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
||||
const [x, y] = [parts(a), parts(b)];
|
||||
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
||||
if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) > (y[i] ?? 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A .deb or source build can't replace itself; ask GitHub what the latest release is instead. */
|
||||
async function latestFromGitHub() {
|
||||
const r = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, { headers: { Accept: "application/vnd.github+json" } });
|
||||
if (!r.ok) throw new Error(`GitHub answered ${r.status}`);
|
||||
const rel = await r.json();
|
||||
return { version: rel.tag_name.replace(/^v/, ""), notes: rel.body ?? "" };
|
||||
}
|
||||
|
||||
export async function checkForUpdates() {
|
||||
if (updates.checking || updates.installing) return;
|
||||
updates.checking = true;
|
||||
updates.lastError = null;
|
||||
try {
|
||||
updates.current ??= await api.appVersion();
|
||||
updates.kind ??= await api.installKind();
|
||||
if (updates.kind === "system") {
|
||||
const rel = await latestFromGitHub();
|
||||
updates.available = newer(rel.version, updates.current) ? { ...rel, canInstall: false } : null;
|
||||
} else {
|
||||
handle = await api.checkUpdate();
|
||||
updates.available = handle ? { version: handle.version, notes: handle.body ?? "", canInstall: true } : null;
|
||||
}
|
||||
} catch (e) {
|
||||
updates.lastError = String(e?.message ?? e);
|
||||
} finally {
|
||||
updates.checking = false;
|
||||
updates.lastChecked = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
/** Downloads, installs and restarts. On Windows the installer closes the app itself. */
|
||||
export async function installUpdate() {
|
||||
if (!handle) return;
|
||||
updates.installing = true;
|
||||
updates.progress = { got: 0, total: 0 };
|
||||
try {
|
||||
await handle.downloadAndInstall((ev) => {
|
||||
if (ev.event === "Started") updates.progress = { got: 0, total: ev.data.contentLength ?? 0 };
|
||||
else if (ev.event === "Progress") updates.progress = { ...updates.progress, got: updates.progress.got + ev.data.chunkLength };
|
||||
});
|
||||
await api.relaunch();
|
||||
} catch (e) {
|
||||
updates.lastError = String(e?.message ?? e);
|
||||
updates.installing = false;
|
||||
updates.progress = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function releasePage(version) {
|
||||
return `https://github.com/${REPO}/releases/tag/v${version}`;
|
||||
}
|
||||
|
||||
/** Checks now and then every six hours while `enabled()` stays true. Returns a stop function. */
|
||||
export function startAutoCheck(enabled) {
|
||||
if (enabled()) checkForUpdates();
|
||||
const t = setInterval(() => enabled() && checkForUpdates(), SIX_HOURS);
|
||||
return () => clearInterval(t);
|
||||
}
|
||||
Reference in new issue
Block a user