feat(desktop): Update in der App — Herunterladen, Installieren und Neustart per Klick im Infobereich (tauri-plugin-updater)
- tauri-plugin-updater 2.11 + semver eingebunden; pubkey, installMode passive und createUpdaterArtifacts in tauri.conf.json (kein endpoints, keine dangerous*-Schalter) - Versionspruefung laeuft ueber updater_builder().check() gegen /api-proxy/desktop/update mit eigenem version_comparator (is_update_newer: hoehere Basis oder anderer Beta-Stempel beta.g<sha7>; gleiche Basis Live → kein Update); 15 s Pruefung, 600 s Download-Timeout - Tray-Eintrag "Update installieren" laedt mit Fortschritt im Menuetext, prueft die minisign-Signatur, installiert (Windows NSIS passiv, Linux AppImage an Ort und Stelle) und startet neu; Fehler → Benachrichtigung und Rueckfall auf die Einstellungsseite im Browser - http-Server im Release: Menuetext "Update nur über https möglich", gesperrt - 15 neue Tests, 2 umgestellt (33 gesamt); gen/schemas vom Bau nachgezogen Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+310
-6
@@ -47,6 +47,15 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -691,6 +700,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
@@ -1004,6 +1024,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.9"
|
||||
@@ -1884,6 +1914,36 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-macros"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.1"
|
||||
@@ -2087,6 +2147,12 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -2366,6 +2432,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.0",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
@@ -2499,6 +2577,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.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -2988,15 +3080,20 @@ dependencies = [
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@@ -3057,12 +3154,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
@@ -3072,6 +3182,33 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni 0.22.4",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
@@ -3425,6 +3562,22 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.3"
|
||||
@@ -3647,7 +3800,7 @@ dependencies = [
|
||||
"gdkwayland-sys",
|
||||
"gdkx11-sys",
|
||||
"gtk",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
@@ -3680,6 +3833,17 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -3703,7 +3867,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"log",
|
||||
"mime",
|
||||
@@ -3886,6 +4050,39 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b28d8cabdeb0564f03ae261963de4bc3d98321cd3d213e76a81b7d344e5df606"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs 6.0.0",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http",
|
||||
"infer",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest 0.13.4",
|
||||
"rustls",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.60.2",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.4.1"
|
||||
@@ -3911,7 +4108,7 @@ dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"http",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"objc2",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
@@ -3934,7 +4131,7 @@ checksum = "fe41e015bf8fc4d6477ff4926a0ef769dc64ff34c7b0038b6f7cacae892acb5c"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
@@ -4041,6 +4238,7 @@ name = "tessera-desktop"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -4049,6 +4247,7 @@ dependencies = [
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-window-state",
|
||||
]
|
||||
|
||||
@@ -4767,6 +4966,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.38.2"
|
||||
@@ -5026,6 +5234,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -5059,13 +5276,30 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
@@ -5096,6 +5330,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -5108,6 +5348,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -5120,12 +5366,24 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -5138,6 +5396,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -5150,6 +5414,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -5162,6 +5432,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -5174,6 +5450,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
@@ -5247,7 +5529,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"javascriptcore-rs",
|
||||
"jni",
|
||||
"jni 0.21.1",
|
||||
"libc",
|
||||
"ndk",
|
||||
"objc2",
|
||||
@@ -5294,6 +5576,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"
|
||||
@@ -5458,6 +5750,18 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[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.0",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
||||
@@ -20,3 +20,5 @@ reqwest = { version = "0.12", features = ["json"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
semver = "1"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2822,6 +2822,60 @@
|
||||
"const": "store:deny-values",
|
||||
"markdownDescription": "Denies the values command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||
"type": "string",
|
||||
|
||||
@@ -2822,6 +2822,60 @@
|
||||
"const": "store:deny-values",
|
||||
"markdownDescription": "Denies the values command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use semver::Version;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tauri::{
|
||||
menu::{CheckMenuItemBuilder, MenuBuilder, MenuItem, MenuItemBuilder},
|
||||
@@ -8,24 +10,32 @@ use tauri_plugin_autostart::{MacosLauncher, ManagerExt};
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tauri_plugin_store::StoreExt;
|
||||
use tauri_plugin_updater::{Update, UpdaterExt};
|
||||
|
||||
/// Standardtext des Update-Eintrags im Tray-Menü, bevor eine neuere Version
|
||||
/// gefunden wurde. `spawn_version_check` setzt den Eintrag nach einem
|
||||
/// Serverwechsel hierauf zurück, damit kein Hinweis des alten Servers stehen
|
||||
/// bleibt.
|
||||
const UPDATE_ITEM_DEFAULT: &str = "Update herunterladen";
|
||||
/// gefunden wurde. Der Eintrag installiert das Update seit quick-260917-kgc
|
||||
/// direkt in der App (Download, Signaturpruefung, Installation, Neustart);
|
||||
/// den Browser oeffnet er nur noch als Rueckfall. `spawn_version_check`
|
||||
/// setzt den Eintrag nach einem Serverwechsel hierauf zurück, damit kein
|
||||
/// Hinweis des alten Servers stehen bleibt.
|
||||
const UPDATE_ITEM_DEFAULT: &str = "Update installieren";
|
||||
|
||||
/// Menuetext, wenn die gespeicherte Server-Adresse mit `http://` beginnt:
|
||||
/// das Updater-Plugin verweigert im Release-Bau unverschluesselte Endpunkte
|
||||
/// (`Error::InsecureTransportProtocol`). Der Eintrag bleibt gesperrt, der
|
||||
/// Text erklaert dauerhaft, warum -- keine Benachrichtigung bei jedem Start.
|
||||
const UPDATE_ITEM_INSECURE: &str = "Update nur über https möglich";
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct VersionResponse {
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct DesktopLatest {
|
||||
version: String,
|
||||
channel: String,
|
||||
commit: String,
|
||||
}
|
||||
/// Gefundenes Update samt dem Menuetext, der zu ihm gehoert. Wird von
|
||||
/// `spawn_version_check` befuellt und vom Tray-Klick "update" per `take()`
|
||||
/// entnommen -- so kann ein Doppelklick keinen zweiten Download starten.
|
||||
/// `Update` ist Clone + Send + Sync, `app.manage` verlangt das.
|
||||
struct PendingUpdate(Mutex<Option<(Update, String)>>);
|
||||
|
||||
/// Baut die Adresse eines API-Pfads aus der gespeicherten Server-Adresse.
|
||||
/// Die API ist vom Client nur ueber den Web-Ursprung erreichbar
|
||||
@@ -41,10 +51,10 @@ fn api_url(server: &str, path: &str) -> String {
|
||||
/// NUR in die Navigation, niemals in den gespeicherten Server-Wert
|
||||
/// (`server_url` im Store bleibt die reine Adresse); daraus liest die
|
||||
/// Next.js-Middleware (apps/web/src/middleware.ts) das Cookie
|
||||
/// `tessera_desktop`, an dem der Web-Client den Desktop-Client erkennt. Die
|
||||
/// Tray-URL "Update herunterladen" bekommt den Parameter bewusst NICHT --
|
||||
/// sie oeffnet im System-Browser, dort muessen die Download-Links sichtbar
|
||||
/// bleiben.
|
||||
/// `tessera_desktop`, an dem der Web-Client den Desktop-Client erkennt. Der
|
||||
/// Browser-Rueckfall des Tray-Eintrags "Update installieren"
|
||||
/// (`open_download_page`) bekommt den Parameter bewusst NICHT -- er oeffnet
|
||||
/// im System-Browser, dort muessen die Download-Links sichtbar bleiben.
|
||||
fn with_desktop_marker(url: &tauri::Url) -> tauri::Url {
|
||||
let mut marked = url.clone();
|
||||
marked.query_pairs_mut().append_pair("desktop", "1");
|
||||
@@ -54,29 +64,105 @@ fn with_desktop_marker(url: &tauri::Url) -> tauri::Url {
|
||||
/// Liefert (Menuetext, Benachrichtigungstext) fuer den Update-Hinweis. Der
|
||||
/// Beta-Kanal (main) vergibt jedem Commit dieselbe X.Y.Z-Version (D-07,
|
||||
/// desktop-collect.sh) -- hat sich nur der Commit geaendert, waere der reine
|
||||
/// Versionstext ("Version 1.1.0 herunterladen") verwirrend, weil der Client
|
||||
/// bereits genau diese Version hat. Der zweite Zweig nennt darum den
|
||||
/// Commit-Stempel statt der unveraenderten Version.
|
||||
/// Versionstext ("Auf Version 1.1.0 aktualisieren") verwirrend, weil der
|
||||
/// Client bereits genau diese Version hat. Der zweite Zweig nennt darum den
|
||||
/// Commit-Stempel statt der unveraenderten Version. Die Texte sprechen vom
|
||||
/// Aktualisieren, nicht vom Herunterladen: der Klick installiert das Update
|
||||
/// in der App (quick-260917-kgc).
|
||||
fn update_labels(version_changed: bool, version: &str, commit: &str) -> (String, String) {
|
||||
if version_changed {
|
||||
(
|
||||
format!("Version {} herunterladen", version),
|
||||
format!("Auf Version {} aktualisieren", version),
|
||||
format!(
|
||||
"Neue Version {} verfügbar – Download über das Symbol im Infobereich.",
|
||||
"Neue Version {} verfügbar – Aktualisieren über das Symbol im Infobereich.",
|
||||
version
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"Neuen Beta-Stand herunterladen".to_string(),
|
||||
format!("Auf Beta-Stand {} aktualisieren", commit),
|
||||
format!(
|
||||
"Neuer Beta-Stand {} verfügbar – Download über das Symbol im Infobereich.",
|
||||
"Neuer Beta-Stand {} verfügbar – Aktualisieren über das Symbol im Infobereich.",
|
||||
commit
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Liest den Beta-Stempel `beta.g<sha7>` aus dem Prerelease-Teil einer
|
||||
/// Server-Version (`1.2.0-beta.gabc1234` → `abc1234`). Das `g` ist Pflicht:
|
||||
/// ein rein numerischer SHA mit fuehrender Null (`0123456`) waere kein
|
||||
/// gueltiger SemVer-Identifier, `check()` endete dann mit Err (Recherche
|
||||
/// Pitfall 2). Live-Versionen ohne Prerelease und fremde Formate (`rc.1`)
|
||||
/// liefern None.
|
||||
fn beta_commit(release: &Version) -> Option<&str> {
|
||||
release
|
||||
.pre
|
||||
.as_str()
|
||||
.strip_prefix("beta.g")
|
||||
.filter(|sha| !sha.is_empty())
|
||||
}
|
||||
|
||||
/// Vergleichsregel des Updaters -- ersetzt den Standardvergleich des Plugins
|
||||
/// vollstaendig (updater.rs Z. 576-579; SemVer allein saehe
|
||||
/// `1.2.0-beta.gXXXX < 1.2.0` und ein Beta-Client bekaeme nie einen neueren
|
||||
/// Beta-Bau). Regel:
|
||||
/// - Basisversion X.Y.Z des Servers groesser → Update; kleiner → keins.
|
||||
/// - Gleiche Basis mit Beta-Stempel `beta.g<sha7>` → Update genau dann,
|
||||
/// wenn der Client einen Commit-Stempel kennt und er sich unterscheidet.
|
||||
/// Der Beta-Kanal (main) vergibt jedem Commit dieselbe X.Y.Z-Version
|
||||
/// (D-07); ohne den Commit-Vergleich saehe ein Beta-Client zwischen zwei
|
||||
/// Freigabe-Tags nie einen neueren Bau (WR-02, Code-Review Phase 18).
|
||||
/// - Gleiche Basis ohne Prerelease (Live-Freigabe) → KEIN Update. Das ist die
|
||||
/// bisherige Regel: ein Live-Client 1.2.0 bietet sich nie selbst 1.2.0 an
|
||||
/// (keine Update-Schleife). Der Fall "Beta-Client wird auf einen Live-Server
|
||||
/// derselben Basis umgestellt" ist client-seitig nicht entscheidbar (der
|
||||
/// Client kennt nur CARGO_PKG_VERSION und APP_COMMIT, nicht den Tag-Commit
|
||||
/// der Freigabe) und bekommt das Update mit der naechsten Freigabe.
|
||||
/// - Leerer Client-Commit (Quell-Tarball ohne git) → nur Basisvergleich.
|
||||
fn is_update_newer(current: &Version, current_commit: &str, release: &Version) -> bool {
|
||||
let base = |v: &Version| (v.major, v.minor, v.patch);
|
||||
if base(release) > base(current) {
|
||||
return true;
|
||||
}
|
||||
if base(release) < base(current) {
|
||||
return false;
|
||||
}
|
||||
match beta_commit(release) {
|
||||
Some(sha) => !current_commit.is_empty() && sha != current_commit,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Baut die Endpunkt-Adresse fuer das Updater-Plugin aus der gespeicherten
|
||||
/// Server-Adresse. Die Platzhalter `{{target}}`, `{{arch}}` und
|
||||
/// `{{current_version}}` stehen als Rohtext im Wert; `query_pairs_mut`
|
||||
/// kodiert sie zu `%7B%7B…%7D%7D`, und das Plugin ersetzt beide Schreibweisen
|
||||
/// (updater.rs Z. 476-486). `base` ist der Origin, aus dem die API die
|
||||
/// absolute Download-Adresse bildet -- das Plugin verlangt eine `url::Url`,
|
||||
/// relative Pfade scheitern. `api_url` bleibt die einzige Stelle mit dem
|
||||
/// `/api-proxy`-Praefix.
|
||||
fn update_endpoint(server: &str) -> Option<tauri::Url> {
|
||||
let mut url = tauri::Url::parse(&api_url(server, "/desktop/update")).ok()?;
|
||||
url.query_pairs_mut()
|
||||
.append_pair("target", "{{target}}")
|
||||
.append_pair("arch", "{{arch}}")
|
||||
.append_pair("current", "{{current_version}}")
|
||||
.append_pair("base", server.trim_end_matches('/'));
|
||||
Some(url)
|
||||
}
|
||||
|
||||
/// Leitet (Menuetext, Benachrichtigungstext) aus Client- und Server-Version
|
||||
/// ab: eine neue Basis X.Y.Z wird als Version genannt (auch bei Beta), bei
|
||||
/// gleicher Basis der Beta-Stempel.
|
||||
fn release_labels(current: &Version, release: &Version) -> (String, String) {
|
||||
let base = |v: &Version| (v.major, v.minor, v.patch);
|
||||
let version_changed = base(release) != base(current);
|
||||
let version = format!("{}.{}.{}", release.major, release.minor, release.patch);
|
||||
let commit = beta_commit(release).unwrap_or("");
|
||||
update_labels(version_changed, &version, commit)
|
||||
}
|
||||
|
||||
/// Parst eine vom Nutzer eingegebene Server-Adresse und erlaubt nur http/https
|
||||
/// -- dieselben Fehlertexte wie bisher inline in `check_server`. `check_server`,
|
||||
/// `save_server_url` und `open_server` rufen ausschliesslich diesen Helfer;
|
||||
@@ -174,43 +260,159 @@ fn apply_server(app: &AppHandle, url: Option<&str>) {
|
||||
let _ = app.state::<TrayItems>().connected.set_text(line);
|
||||
}
|
||||
|
||||
/// Fragt den Update-Endpunkt ueber das Updater-Plugin ab. Der eigene
|
||||
/// `version_comparator` (`is_update_newer`) ersetzt den Standardvergleich
|
||||
/// des Plugins. Die 15 s gelten fuer die Pruefung; das Plugin uebernimmt
|
||||
/// denselben Wert als GESAMT-Timeout des Downloads (updater.rs Z. 698-700),
|
||||
/// darum setzt `spawn_version_check` vor der Ablage `update.timeout` neu.
|
||||
async fn check_for_update(
|
||||
app: &AppHandle,
|
||||
endpoint: tauri::Url,
|
||||
) -> tauri_plugin_updater::Result<Option<Update>> {
|
||||
let current_commit = env!("APP_COMMIT");
|
||||
app.updater_builder()
|
||||
.endpoints(vec![endpoint])?
|
||||
.timeout(Duration::from_secs(15))
|
||||
.version_comparator(move |current, release| {
|
||||
is_update_newer(¤t, current_commit, &release.version)
|
||||
})
|
||||
.build()?
|
||||
.check()
|
||||
.await
|
||||
}
|
||||
|
||||
/// Versionsprüfung, aus `setup` herausgezogen, damit sie nach einem
|
||||
/// Serverwechsel erneut laufen kann (async, non-blocking wie bisher). Setzt
|
||||
/// den Update-Eintrag zuerst auf den Standardtext zurück -- nach einem
|
||||
/// Wechsel darf kein Hinweis des alten Servers stehen bleiben.
|
||||
/// den Update-Eintrag zuerst auf den Standardtext zurück und leert den
|
||||
/// abgelegten Update-Stand -- nach einem Wechsel darf kein Hinweis des alten
|
||||
/// Servers stehen bleiben.
|
||||
fn spawn_version_check(app: AppHandle, server_url: String) {
|
||||
let update_item = app.state::<TrayItems>().update.clone();
|
||||
let _ = update_item.set_text(UPDATE_ITEM_DEFAULT);
|
||||
let _ = update_item.set_enabled(false);
|
||||
if let Ok(mut pending) = app.state::<PendingUpdate>().0.lock() {
|
||||
*pending = None;
|
||||
}
|
||||
|
||||
// Gespeicherte Adressen sind immer parsebar (parse_server_url) -- reine
|
||||
// Absicherung.
|
||||
let Some(endpoint) = update_endpoint(&server_url) else {
|
||||
return;
|
||||
};
|
||||
// Cargo garantiert eine gueltige SemVer-Version; desktop-version.sh
|
||||
// schreibt immer reines X.Y.Z (D-07).
|
||||
let current = Version::parse(env!("CARGO_PKG_VERSION"))
|
||||
.expect("CARGO_PKG_VERSION muss eine gueltige SemVer-Version sein");
|
||||
|
||||
let app_version = env!("CARGO_PKG_VERSION").to_string();
|
||||
let app_commit = env!("APP_COMMIT").to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let url = api_url(&server_url, "/desktop/latest");
|
||||
if let Ok(resp) = reqwest::get(&url).await {
|
||||
if let Ok(info) = resp.json::<DesktopLatest>().await {
|
||||
// Beta-Kanal (main) vergibt jedem Commit dieselbe X.Y.Z-Version
|
||||
// (D-07, desktop-collect.sh) -- ohne den Commit-Vergleich saehe
|
||||
// ein Beta-Client zwischen zwei Freigabe-Tags nie einen neueren
|
||||
// Bau (WR-02, Code-Review Phase 18). Fuer den Live-Kanal bleibt
|
||||
// es beim reinen Versionsvergleich. Die Texte fuer Menue und
|
||||
// Benachrichtigung liefert `update_labels`.
|
||||
let version_changed = info.version != app_version;
|
||||
let is_newer =
|
||||
version_changed || (info.channel == "beta" && info.commit != app_commit);
|
||||
if is_newer {
|
||||
let (menu_text, body) =
|
||||
update_labels(version_changed, &info.version, &info.commit);
|
||||
match check_for_update(&app, endpoint).await {
|
||||
Ok(Some(mut update)) => {
|
||||
let (menu_text, body) = Version::parse(&update.version)
|
||||
.map(|release| release_labels(¤t, &release))
|
||||
.unwrap_or_else(|_| update_labels(true, &update.version, ""));
|
||||
// 15 s reichen fuer die Pruefung, nicht fuer den ~100-MB-Download:
|
||||
// das Plugin wendet `Update.timeout` als Gesamt-Timeout des
|
||||
// Downloads an (updater.rs Z. 698-700).
|
||||
update.timeout = Some(Duration::from_secs(600));
|
||||
let _ = app
|
||||
.notification()
|
||||
.builder()
|
||||
.title("Tessera-Update")
|
||||
.body(body)
|
||||
.show();
|
||||
let _ = update_item.set_text(menu_text);
|
||||
let _ = update_item.set_text(&menu_text);
|
||||
let _ = update_item.set_enabled(true);
|
||||
if let Ok(mut pending) = app.state::<PendingUpdate>().0.lock() {
|
||||
*pending = Some((update, menu_text));
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
// Der Release-Bau verweigert http-Endpunkte (config.rs
|
||||
// `validate_endpoints`), der Debug-Bau warnt nur. Keine
|
||||
// `dangerousInsecureTransportProtocol`-Freigabe (T-KGC-06): der
|
||||
// Eintrag bleibt gesperrt und erklaert den Grund.
|
||||
Err(tauri_plugin_updater::Error::InsecureTransportProtocol) => {
|
||||
let _ = update_item.set_text(UPDATE_ITEM_INSECURE);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Oeffnet die Seite Einstellungen → Desktop-App im System-Browser -- der
|
||||
/// bisherige Weg des Tray-Eintrags, jetzt Rueckfall, wenn kein Update
|
||||
/// abgelegt ist oder die Installation in der App fehlschlaegt. Bewusst ohne
|
||||
/// `desktop=1` (siehe `with_desktop_marker`).
|
||||
fn open_download_page(app: &AppHandle) {
|
||||
if let Some(server) = stored_server_url(app) {
|
||||
let target = format!("{}/settings/general/desktop", server.trim_end_matches('/'));
|
||||
let _ = app.opener().open_url(target, None::<&str>);
|
||||
}
|
||||
}
|
||||
|
||||
/// Laedt das abgelegte Update, prueft die Signatur, installiert es und
|
||||
/// startet die App neu. Fortschritt und Zustand stehen im Menuetext des
|
||||
/// gesperrten Eintrags. Fehler: Menuetext und Stand zurueck, Eintrag wieder
|
||||
/// aktiv, Benachrichtigung mit Grund, Einstellungsseite im Browser als
|
||||
/// Rueckfall.
|
||||
fn spawn_update_install(app: AppHandle, update: Update, menu_text: String) {
|
||||
let item = app.state::<TrayItems>().update.clone();
|
||||
let _ = item.set_enabled(false);
|
||||
let _ = item.set_text("Update wird heruntergeladen…");
|
||||
let _ = app
|
||||
.notification()
|
||||
.builder()
|
||||
.title("Tessera-Update")
|
||||
.body("Update wird heruntergeladen…")
|
||||
.show();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let item_progress = item.clone();
|
||||
let item_finish = item.clone();
|
||||
let mut received: u64 = 0;
|
||||
let mut last_percent: Option<u64> = None;
|
||||
let on_chunk = move |chunk: usize, total: Option<u64>| {
|
||||
received += chunk as u64;
|
||||
if let Some(total) = total.filter(|t| *t > 0) {
|
||||
let percent = (received * 100 / total).min(100);
|
||||
if last_percent != Some(percent) {
|
||||
last_percent = Some(percent);
|
||||
let _ = item_progress.set_text(format!("Lädt … {} %", percent));
|
||||
}
|
||||
}
|
||||
};
|
||||
let on_finish = move || {
|
||||
let _ = item_finish.set_text("Wird installiert…");
|
||||
};
|
||||
|
||||
match update.download_and_install(on_chunk, on_finish).await {
|
||||
Ok(()) => {
|
||||
// Windows: nie erreicht -- das Plugin startet den NSIS-Installer
|
||||
// mit `/P /UPDATE /R /ARGS` und beendet den Prozess per
|
||||
// `std::process::exit(0)`; der Run-Handler wird dabei nicht
|
||||
// durchlaufen. Linux: die AppImage ist ersetzt; `restart()` aus
|
||||
// dem Nebenthread laeuft ueber RESTART_EXIT_CODE
|
||||
// (`code: Some(..)`) am `prevent_exit` des Run-Handlers vorbei
|
||||
// (tauri app.rs Z. 588-611).
|
||||
app.restart();
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = item.set_text(&menu_text);
|
||||
if let Ok(mut pending) = app.state::<PendingUpdate>().0.lock() {
|
||||
*pending = Some((update, menu_text.clone()));
|
||||
}
|
||||
let _ = item.set_enabled(true);
|
||||
let _ = app
|
||||
.notification()
|
||||
.builder()
|
||||
.title("Tessera-Update")
|
||||
.body(format!(
|
||||
"Update fehlgeschlagen: {}. Die Download-Seite wird im Browser geöffnet.",
|
||||
e
|
||||
))
|
||||
.show();
|
||||
open_download_page(&app);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -298,6 +500,7 @@ pub fn run() {
|
||||
None,
|
||||
))
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
check_server,
|
||||
save_server_url,
|
||||
@@ -316,11 +519,13 @@ pub fn run() {
|
||||
}
|
||||
|
||||
// Tray menu: Verbunden mit ... (gesperrt) · — · Öffnen ·
|
||||
// Server-Adresse ändern... · Update herunterladen · — ·
|
||||
// Server-Adresse ändern... · Update installieren · — ·
|
||||
// Autostart-Haken · — · Beenden.
|
||||
// "connected" ist nicht anklickbar; "update" bleibt gesperrt, bis
|
||||
// die Versionspruefung eine neuere Version findet; "autostart"
|
||||
// spiegelt den tatsaechlichen Systemzustand beim Start.
|
||||
// die Versionspruefung eine neuere Version findet, und heisst dann
|
||||
// "Auf Version X.Y.Z aktualisieren" bzw. "Auf Beta-Stand ...
|
||||
// aktualisieren"; "autostart" spiegelt den tatsaechlichen
|
||||
// Systemzustand beim Start.
|
||||
let connected =
|
||||
MenuItemBuilder::with_id("connected", tray_labels(server_url.as_deref()).1)
|
||||
.enabled(false)
|
||||
@@ -357,6 +562,7 @@ pub fn run() {
|
||||
connected: connected.clone(),
|
||||
update: update.clone(),
|
||||
});
|
||||
app.manage(PendingUpdate(Mutex::new(None)));
|
||||
|
||||
let autostart_for_menu = autostart.clone();
|
||||
|
||||
@@ -383,12 +589,20 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
"update" => {
|
||||
if let Some(server) = stored_server_url(app) {
|
||||
let target = format!(
|
||||
"{}/settings/general/desktop",
|
||||
server.trim_end_matches('/')
|
||||
);
|
||||
let _ = app.opener().open_url(target, None::<&str>);
|
||||
// `take()` verhindert Doppelklick-Downloads, der
|
||||
// gesperrte Eintrag ebenso; ohne abgelegten Stand
|
||||
// bleibt der Browser-Weg (Einstellungen → Desktop-App).
|
||||
let pending = app
|
||||
.state::<PendingUpdate>()
|
||||
.0
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|mut guard| guard.take());
|
||||
match pending {
|
||||
Some((update, menu_text)) => {
|
||||
spawn_update_install(app.clone(), update, menu_text)
|
||||
}
|
||||
None => open_download_page(app),
|
||||
}
|
||||
}
|
||||
"autostart" => {
|
||||
@@ -489,23 +703,124 @@ mod tests {
|
||||
#[test]
|
||||
fn update_labels_bei_versionswechsel() {
|
||||
let (menu_text, body) = update_labels(true, "1.2.0", "abc1234");
|
||||
assert_eq!(menu_text, "Version 1.2.0 herunterladen");
|
||||
assert_eq!(menu_text, "Auf Version 1.2.0 aktualisieren");
|
||||
assert_eq!(
|
||||
body,
|
||||
"Neue Version 1.2.0 verfügbar – Download über das Symbol im Infobereich."
|
||||
"Neue Version 1.2.0 verfügbar – Aktualisieren über das Symbol im Infobereich."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_labels_bei_gleicher_version_beta_commit() {
|
||||
let (menu_text, body) = update_labels(false, "1.1.0", "abc1234");
|
||||
assert_eq!(menu_text, "Neuen Beta-Stand herunterladen");
|
||||
assert_eq!(menu_text, "Auf Beta-Stand abc1234 aktualisieren");
|
||||
assert_eq!(
|
||||
body,
|
||||
"Neuer Beta-Stand abc1234 verfügbar – Download über das Symbol im Infobereich."
|
||||
"Neuer Beta-Stand abc1234 verfügbar – Aktualisieren über das Symbol im Infobereich."
|
||||
);
|
||||
}
|
||||
|
||||
fn v(s: &str) -> Version {
|
||||
Version::parse(s).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_hoehere_basis_live() {
|
||||
assert!(is_update_newer(&v("1.2.0"), "abc1234", &v("1.3.0")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_hoehere_basis_beta() {
|
||||
assert!(is_update_newer(
|
||||
&v("1.2.0"),
|
||||
"abc1234",
|
||||
&v("1.3.0-beta.gdef5678")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_niedrigere_basis() {
|
||||
assert!(!is_update_newer(
|
||||
&v("1.3.0"),
|
||||
"abc1234",
|
||||
&v("1.2.0-beta.g9999999")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_gleiche_basis_anderer_beta_stempel() {
|
||||
assert!(is_update_newer(
|
||||
&v("1.2.0"),
|
||||
"abc1234",
|
||||
&v("1.2.0-beta.gdef5678")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_gleiche_basis_gleicher_beta_stempel() {
|
||||
assert!(!is_update_newer(
|
||||
&v("1.2.0"),
|
||||
"abc1234",
|
||||
&v("1.2.0-beta.gabc1234")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_gleiche_basis_live_ohne_prerelease() {
|
||||
assert!(!is_update_newer(&v("1.2.0"), "abc1234", &v("1.2.0")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_leerer_client_commit_nur_basisvergleich() {
|
||||
assert!(!is_update_newer(&v("1.2.0"), "", &v("1.2.0-beta.gdef5678")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_update_newer_fremdes_prerelease_format() {
|
||||
assert!(!is_update_newer(&v("1.2.0"), "abc1234", &v("1.2.0-rc.1")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beta_commit_mit_praefix_g_erlaubt_fuehrende_null() {
|
||||
assert!(Version::parse("1.2.0-beta.g0123456").is_ok());
|
||||
assert_eq!(beta_commit(&v("1.2.0-beta.g0123456")), Some("0123456"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beta_commit_none_ohne_prerelease() {
|
||||
assert_eq!(beta_commit(&v("1.2.0")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beta_commit_none_bei_fremdem_prerelease() {
|
||||
assert_eq!(beta_commit(&v("1.2.0-rc.1")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_endpoint_traegt_platzhalter_und_base() {
|
||||
assert_eq!(
|
||||
update_endpoint("https://tessera.ctl.de/").unwrap().as_str(),
|
||||
"https://tessera.ctl.de/api-proxy/desktop/update?target=%7B%7Btarget%7D%7D&arch=%7B%7Barch%7D%7D¤t=%7B%7Bcurrent_version%7D%7D&base=https%3A%2F%2Ftessera.ctl.de"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_endpoint_none_bei_unparsbarer_adresse() {
|
||||
assert!(update_endpoint("kein url").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_labels_neue_basis_nennt_version_auch_bei_beta() {
|
||||
let (menu_text, _) = release_labels(&v("1.2.0"), &v("1.3.0-beta.gdef5678"));
|
||||
assert_eq!(menu_text, "Auf Version 1.3.0 aktualisieren");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_labels_gleiche_basis_nennt_beta_stempel() {
|
||||
let (menu_text, _) = release_labels(&v("1.2.0"), &v("1.2.0-beta.gdef5678"));
|
||||
assert_eq!(menu_text, "Auf Beta-Stand def5678 aktualisieren");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_host_mit_pfad_und_ohne_port() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"createUpdaterArtifacts": true,
|
||||
"targets": [
|
||||
"appimage",
|
||||
"nsis"
|
||||
@@ -48,5 +49,13 @@
|
||||
"installMode": "currentUser"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEIxNDIxMTZDMUFGRkJCRjEKUldUeHUvOGFiQkZDc2V0cnJZcmJVSURraGJHcE5TODVDeUp3UEVpM0xOc3I5TUZrNW5qZUw0d2gK",
|
||||
"windows": {
|
||||
"installMode": "passive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user