diff --git a/components/GameStatusButton.vue b/components/GameStatusButton.vue index a10fd27..65b0ab1 100644 --- a/components/GameStatusButton.vue +++ b/components/GameStatusButton.vue @@ -61,6 +61,7 @@ import { ChevronDownIcon, PlayIcon, QueueListIcon, + StopIcon, WrenchIcon, } from "@heroicons/vue/20/solid"; @@ -128,7 +129,7 @@ const buttonIcons: { [key in GameStatusEnum]: Component } = { [GameStatusEnum.Installed]: PlayIcon, [GameStatusEnum.Updating]: ArrowDownTrayIcon, [GameStatusEnum.Uninstalling]: TrashIcon, - [GameStatusEnum.Running]: PlayIcon, + [GameStatusEnum.Running]: StopIcon, [GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon }; diff --git a/components/LibrarySearch.vue b/components/LibrarySearch.vue index 2ae8305..9ea0c57 100644 --- a/components/LibrarySearch.vue +++ b/components/LibrarySearch.vue @@ -1,19 +1,30 @@ + diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9f4a86e..286493f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -68,6 +68,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "ashpd" version = "0.11.0" @@ -427,15 +433,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "binary-stream" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ef03ef225ea9a0b680a5926a58cb45d8eb56abf23d8a8b5c5dbc61235e2dac" -dependencies = [ - "thiserror 1.0.69", -] - [[package]] name = "bincode" version = "1.3.3" @@ -445,6 +442,30 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcode" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf300f4aa6e66f3bdff11f1236a88c622fe47ea814524792240b4d554d9858ee" +dependencies = [ + "arrayvec", + "bitcode_derive", + "bytemuck", + "glam", + "serde", +] + +[[package]] +name = "bitcode_derive" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b6b4cb608b8282dc3b53d0f4c9ab404655d562674c682db7e6c0458cc83c23" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -538,9 +559,9 @@ checksum = "66bb12751a83493ef4b8da1120451a262554e216a247f14b48cb5e8fe7ed8bdf" [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -549,9 +570,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -946,15 +967,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", + "itoa", "matches", - "phf 0.8.0", + "phf 0.10.1", "proc-macro2", "quote", "smallvec", @@ -1250,6 +1271,7 @@ name = "drop-app" version = "0.3.0-rc-7" dependencies = [ "atomic-instant-full", + "bitcode", "boxcar", "cacache 13.1.0", "chrono", @@ -1278,7 +1300,6 @@ dependencies = [ "rustix 0.38.44", "schemars", "serde", - "serde-binary", "serde_json", "serde_with", "sha1", @@ -1290,6 +1311,7 @@ dependencies = [ "tauri-plugin-autostart", "tauri-plugin-deep-link", "tauri-plugin-dialog", + "tauri-plugin-opener", "tauri-plugin-os", "tauri-plugin-shell", "tauri-plugin-single-instance", @@ -1906,6 +1928,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "glam" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" + [[package]] name = "glib" version = "0.18.5" @@ -2131,16 +2159,14 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", + "match_token", ] [[package]] @@ -2151,7 +2177,7 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -2162,7 +2188,7 @@ checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -2271,7 +2297,7 @@ dependencies = [ "http-body 0.4.6", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -2293,7 +2319,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "httparse", - "itoa 1.0.15", + "itoa", "pin-project-lite", "smallvec", "tokio", @@ -2563,12 +2589,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.15" @@ -2684,14 +2704,13 @@ dependencies = [ [[package]] name = "kuchikiki" -version = "0.8.2" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.3", - "matches", + "indexmap 2.9.0", "selectors", ] @@ -2856,18 +2875,29 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "matches" version = "0.1.10" @@ -2982,9 +3012,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" dependencies = [ "crossbeam-channel", "dpi", @@ -3588,9 +3618,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", "phf_shared 0.8.0", - "proc-macro-hack", ] [[package]] @@ -3599,7 +3627,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros 0.10.0", "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] @@ -3624,12 +3654,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -3664,12 +3694,12 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2", "quote", @@ -4558,22 +4588,20 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "selectors" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", "log", - "matches", "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -4594,17 +4622,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-binary" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b550db407b83ed53a4f76f888bfd7441b685abc2c086e20fb47781a286940506" -dependencies = [ - "binary-stream", - "serde", - "thiserror 1.0.69", -] - [[package]] name = "serde-untagged" version = "0.1.7" @@ -4654,7 +4671,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "itoa 1.0.15", + "itoa", "memchr", "ryu", "serde", @@ -4687,7 +4704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.15", + "itoa", "ryu", "serde", ] @@ -4729,7 +4746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap 2.9.0", - "itoa 1.0.15", + "itoa", "ryu", "serde", "unsafe-libyaml", @@ -4759,9 +4776,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ "nodrop", "stable_deref_trait", @@ -5162,9 +5179,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" +checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" dependencies = [ "bitflags 2.9.1", "core-foundation 0.10.1", @@ -5238,17 +5255,16 @@ dependencies = [ [[package]] name = "tauri" -version = "2.5.1" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" +checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" dependencies = [ "anyhow", "bytes", "dirs 6.0.0", "dunce", "embed_plist", - "futures-util", - "getrandom 0.2.16", + "getrandom 0.3.3", "glob", "gtk", "heck 0.5.0", @@ -5290,9 +5306,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" +checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" dependencies = [ "anyhow", "cargo_toml", @@ -5312,9 +5328,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" +checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" dependencies = [ "base64 0.22.1", "brotli", @@ -5339,9 +5355,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" +checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -5353,9 +5369,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601" +checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" dependencies = [ "anyhow", "glob", @@ -5442,6 +5458,28 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-opener" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "open", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "url", + "windows", + "zbus", +] + [[package]] name = "tauri-plugin-os" version = "2.2.1" @@ -5499,9 +5537,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" +checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" dependencies = [ "cookie", "dpi", @@ -5521,9 +5559,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" +checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" dependencies = [ "gtk", "http 1.3.1", @@ -5548,9 +5586,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" +checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" dependencies = [ "anyhow", "brotli", @@ -5619,12 +5657,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.69" @@ -5694,7 +5726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa 1.0.15", + "itoa", "num-conv", "powerfmt", "serde", @@ -5945,9 +5977,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" dependencies = [ "crossbeam-channel", "dirs 6.0.0", @@ -6438,9 +6470,9 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", @@ -6463,9 +6495,9 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ "thiserror 2.0.12", "windows", @@ -6938,9 +6970,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wry" -version = "0.51.2" +version = "0.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" +checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" dependencies = [ "base64 0.22.1", "block2 0.6.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9268f9a..2b9b554 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,7 +3,7 @@ name = "drop-app" version = "0.3.0-rc-7" description = "The client application for the open-source, self-hosted game distribution platform Drop" authors = ["Drop OSS"] -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -25,7 +25,6 @@ tauri-build = { version = "2.0.0", features = [] } [dependencies] tauri-plugin-shell = "2.2.1" serde_json = "1" -serde-binary = "0.5.0" rayon = "1.10.0" webbrowser = "1.0.2" url = "2.5.2" @@ -67,6 +66,8 @@ filetime = "0.2.25" walkdir = "2.5.0" known-folders = "1.2.0" native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] } +tauri-plugin-opener = "2.4.0" +bitcode = "0.6.6" # tailscale = { path = "./tailscale" } [dependencies.dynfmt] diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 1b818b3..ac70625 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -14,6 +14,7 @@ "core:window:allow-close", "deep-link:default", "dialog:default", - "os:default" + "os:default", + "opener:default" ] } \ No newline at end of file diff --git a/src-tauri/src/download_manager/download_manager_builder.rs b/src-tauri/src/download_manager/download_manager_builder.rs index 4d03712..c50ad20 100644 --- a/src-tauri/src/download_manager/download_manager_builder.rs +++ b/src-tauri/src/download_manager/download_manager_builder.rs @@ -1,10 +1,10 @@ use std::{ collections::HashMap, sync::{ - mpsc::{channel, Receiver, Sender}, Arc, Mutex, + mpsc::{Receiver, Sender, channel}, }, - thread::{spawn, JoinHandle}, + thread::{JoinHandle, spawn}, }; use log::{debug, error, info, warn}; @@ -212,13 +212,13 @@ impl DownloadManagerBuilder { if self.current_download_agent.is_some() && self.download_queue.read().front().unwrap() == &self.current_download_agent.as_ref().unwrap().metadata() - { - debug!( - "Current download agent: {:?}", - self.current_download_agent.as_ref().unwrap().metadata() - ); - return; - } + { + debug!( + "Current download agent: {:?}", + self.current_download_agent.as_ref().unwrap().metadata() + ); + return; + } debug!("current download queue: {:?}", self.download_queue.read()); @@ -295,11 +295,12 @@ impl DownloadManagerBuilder { } fn manage_completed_signal(&mut self, meta: DownloadableMetadata) { debug!("got signal Completed"); - if let Some(interface) = &self.current_download_agent { - if interface.metadata() == meta { - self.remove_and_cleanup_front_download(&meta); - } + if let Some(interface) = &self.current_download_agent + && interface.metadata() == meta + { + self.remove_and_cleanup_front_download(&meta); } + self.push_ui_queue_update(); self.sender.send(DownloadManagerSignal::Go).unwrap(); } diff --git a/src-tauri/src/download_manager/download_manager_frontend.rs b/src-tauri/src/download_manager/download_manager_frontend.rs index 21892b9..359903d 100644 --- a/src-tauri/src/download_manager/download_manager_frontend.rs +++ b/src-tauri/src/download_manager/download_manager_frontend.rs @@ -148,9 +148,7 @@ impl DownloadManager { .unwrap(); } - debug!( - "moving download at index {current_index} to index {new_index}" - ); + debug!("moving download at index {current_index} to index {new_index}"); let mut queue = self.edit(); let to_move = queue.remove(current_index).unwrap(); diff --git a/src-tauri/src/download_manager/mod.rs b/src-tauri/src/download_manager/mod.rs index b21961f..756a1ad 100644 --- a/src-tauri/src/download_manager/mod.rs +++ b/src-tauri/src/download_manager/mod.rs @@ -1,5 +1,5 @@ pub mod commands; -pub mod download_manager_frontend; pub mod download_manager_builder; +pub mod download_manager_frontend; pub mod downloadable; pub mod util; diff --git a/src-tauri/src/error/application_download_error.rs b/src-tauri/src/error/application_download_error.rs index 7ea8bf4..c1f3899 100644 --- a/src-tauri/src/error/application_download_error.rs +++ b/src-tauri/src/error/application_download_error.rs @@ -5,7 +5,7 @@ use std::{ use serde_with::SerializeDisplay; -use super::{remote_access_error::RemoteAccessError}; +use super::remote_access_error::RemoteAccessError; // TODO: Rename / separate from downloads #[derive(Debug, SerializeDisplay)] diff --git a/src-tauri/src/error/process_error.rs b/src-tauri/src/error/process_error.rs index 99bf9ba..c04f651 100644 --- a/src-tauri/src/error/process_error.rs +++ b/src-tauri/src/error/process_error.rs @@ -13,6 +13,7 @@ pub enum ProcessError { IOError(Error), FormatError(String), // String errors supremacy InvalidPlatform, + OpenerError(tauri_plugin_opener::Error) } impl Display for ProcessError { @@ -22,12 +23,13 @@ impl Display for ProcessError { ProcessError::NotInstalled => "Game not installed", ProcessError::AlreadyRunning => "Game already running", ProcessError::NotDownloaded => "Game not downloaded", - ProcessError::InvalidID => "Invalid Game ID", - ProcessError::InvalidVersion => "Invalid Game version", + ProcessError::InvalidID => "Invalid game ID", + ProcessError::InvalidVersion => "Invalid game version", ProcessError::IOError(error) => &error.to_string(), - ProcessError::InvalidPlatform => "This Game cannot be played on the current platform", + ProcessError::InvalidPlatform => "This game cannot be played on the current platform", ProcessError::FormatError(e) => &format!("Failed to format template: {e}"), - }; + ProcessError::OpenerError(error) => &format!("Failed to open directory: {error}"), + }; write!(f, "{s}") } } diff --git a/src-tauri/src/games/commands.rs b/src-tauri/src/games/commands.rs index adcd6be..8a95132 100644 --- a/src-tauri/src/games/commands.rs +++ b/src-tauri/src/games/commands.rs @@ -37,14 +37,13 @@ pub fn fetch_game( game_id: String, state: tauri::State<'_, Mutex>, ) -> Result { - let res = offline!( + offline!( state, fetch_game_logic, fetch_game_logic_offline, game_id, state - ); - res + ) } #[tauri::command] diff --git a/src-tauri/src/games/downloads/commands.rs b/src-tauri/src/games/downloads/commands.rs index 1bead28..7e48162 100644 --- a/src-tauri/src/games/downloads/commands.rs +++ b/src-tauri/src/games/downloads/commands.rs @@ -5,7 +5,9 @@ use std::{ use crate::{ database::{db::borrow_db_checked, models::data::GameDownloadStatus}, - download_manager::{download_manager_frontend::DownloadManagerSignal, downloadable::Downloadable}, + download_manager::{ + download_manager_frontend::DownloadManagerSignal, downloadable::Downloadable, + }, error::download_manager_error::DownloadManagerError, AppState, }; diff --git a/src-tauri/src/games/library.rs b/src-tauri/src/games/library.rs index 968dc49..53041ad 100644 --- a/src-tauri/src/games/library.rs +++ b/src-tauri/src/games/library.rs @@ -19,6 +19,7 @@ use crate::remote::auth::generate_authorization_header; use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db}; use crate::remote::requests::make_request; use crate::AppState; +use bitcode::{Encode, Decode}; #[derive(Serialize, Deserialize, Debug)] pub struct FetchGameStruct { @@ -27,7 +28,7 @@ pub struct FetchGameStruct { version: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, Encode, Decode)] #[serde(rename_all = "camelCase")] pub struct Game { id: String, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a4d0fab..838ebd9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ #![feature(fn_traits)] +#![feature(duration_constructors)] #![deny(clippy::all)] mod database; @@ -10,6 +11,7 @@ mod error; mod process; mod remote; +use crate::process::commands::open_process_logs; use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download}; use client::commands::fetch_state; use client::{ @@ -25,8 +27,8 @@ use database::models::data::GameDownloadStatus; use download_manager::commands::{ cancel_game, move_download_in_queue, pause_downloads, resume_downloads, }; -use download_manager::download_manager_frontend::DownloadManager; use download_manager::download_manager_builder::DownloadManagerBuilder; +use download_manager::download_manager_frontend::DownloadManager; use games::collections::commands::{ add_game_to_collection, create_collection, delete_collection, delete_game_in_collection, fetch_collection, fetch_collections, @@ -69,6 +71,7 @@ use tauri::tray::TrayIconBuilder; use tauri::{AppHandle, Manager, RunEvent, WindowEvent}; use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_dialog::DialogExt; +use bitcode::{Encode, Decode}; #[derive(Clone, Copy, Serialize, Eq, PartialEq)] pub enum AppStatus { @@ -81,7 +84,7 @@ pub enum AppStatus { ServerUnavailable, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "camelCase")] pub struct User { id: String, @@ -225,7 +228,8 @@ pub fn custom_panic_handler(e: &PanicHookInfo) -> Option<()> { .as_secs() )); let mut file = File::create_new(crash_file).ok()?; - file.write_all(format!("Drop crashed with the following panic:\n{e}").as_bytes()).ok()?; + file.write_all(format!("Drop crashed with the following panic:\n{e}").as_bytes()) + .ok()?; drop(file); Some(()) @@ -235,11 +239,11 @@ pub fn custom_panic_handler(e: &PanicHookInfo) -> Option<()> { pub fn run() { panic::set_hook(Box::new(|e| { let _ = custom_panic_handler(e); - let dft = panic::take_hook(); - dft.call((e,)); + println!("{e}"); })); let mut builder = tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_dialog::init()); @@ -299,6 +303,7 @@ pub fn run() { kill_game, toggle_autostart, get_autostart_enabled, + open_process_logs ]) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) diff --git a/src-tauri/src/process/commands.rs b/src-tauri/src/process/commands.rs index 8a213a4..93cd86a 100644 --- a/src-tauri/src/process/commands.rs +++ b/src-tauri/src/process/commands.rs @@ -38,3 +38,13 @@ pub fn kill_game( .kill_game(game_id) .map_err(ProcessError::IOError) } + +#[tauri::command] +pub fn open_process_logs( + game_id: String, + state: tauri::State<'_, Mutex>, +) -> Result<(), ProcessError> { + let state_lock = state.lock().unwrap(); + let mut process_manager_lock = state_lock.process_manager.lock().unwrap(); + process_manager_lock.open_process_logs(game_id) +} diff --git a/src-tauri/src/process/process_manager.rs b/src-tauri/src/process/process_manager.rs index be82273..6eb45c8 100644 --- a/src-tauri/src/process/process_manager.rs +++ b/src-tauri/src/process/process_manager.rs @@ -1,12 +1,13 @@ use std::{ collections::HashMap, - fs::OpenOptions, + fs::{OpenOptions, create_dir_all}, io::{self}, path::PathBuf, process::{Command, ExitStatus}, str::FromStr, sync::{Arc, Mutex}, thread::spawn, + time::{Duration, SystemTime}, }; use dynfmt::Format; @@ -14,11 +15,13 @@ use dynfmt::SimpleCurlyFormat; use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; use shared_child::SharedChild; -use tauri::{AppHandle, Manager}; +use tauri::{AppHandle, Emitter, Manager}; +use tauri_plugin_opener::OpenerExt; use crate::{ + AppState, DB, database::{ - db::{borrow_db_mut_checked, DATA_ROOT_DIR}, + db::{DATA_ROOT_DIR, borrow_db_mut_checked}, models::data::{ ApplicationTransientStatus, DownloadType, DownloadableMetadata, GameDownloadStatus, GameVersion, @@ -26,13 +29,18 @@ use crate::{ }, error::process_error::ProcessError, games::{library::push_game_update, state::GameStatusManager}, - AppState, DB, }; +pub struct RunningProcess { + handle: Arc, + start: SystemTime, + manually_killed: bool, +} + pub struct ProcessManager<'a> { current_platform: Platform, log_output_dir: PathBuf, - processes: HashMap>, + processes: HashMap, app_handle: AppHandle, game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>, } @@ -77,10 +85,11 @@ impl ProcessManager<'_> { } pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> { - match self.processes.get(&game_id) { - Some(child) => { - child.kill()?; - child.wait()?; + match self.processes.get_mut(&game_id) { + Some(process) => { + process.manually_killed = true; + process.handle.kill()?; + process.handle.wait()?; Ok(()) } None => Err(io::Error::new( @@ -90,15 +99,26 @@ impl ProcessManager<'_> { } } + pub fn open_process_logs(&mut self, game_id: String) -> Result<(), ProcessError> { + let dir = self.log_output_dir.join(game_id); + self.app_handle + .opener() + .open_path(dir.to_str().unwrap(), None::<&str>) + .map_err(ProcessError::OpenerError)?; + Ok(()) + } + fn on_process_finish(&mut self, game_id: String, result: Result) { if !self.processes.contains_key(&game_id) { - warn!("process on_finish was called, but game_id is no longer valid. finished with result: {result:?}"); + warn!( + "process on_finish was called, but game_id is no longer valid. finished with result: {result:?}" + ); return; } debug!("process for {:?} exited with {:?}", &game_id, result); - self.processes.remove(&game_id); + let process = self.processes.remove(&game_id).unwrap(); let mut db_handle = borrow_db_mut_checked(); let meta = db_handle @@ -114,26 +134,32 @@ impl ProcessManager<'_> { version_name, install_dir, }) = current_state + && let Ok(exit_code) = result + && exit_code.success() { - if let Ok(exit_code) = result { - if exit_code.success() { - db_handle.applications.game_statuses.insert( - game_id.clone(), - GameDownloadStatus::Installed { - version_name: version_name.to_string(), - install_dir: install_dir.to_string(), - }, - ); - } - } + db_handle.applications.game_statuses.insert( + game_id.clone(), + GameDownloadStatus::Installed { + version_name: version_name.to_string(), + install_dir: install_dir.to_string(), + }, + ); } drop(db_handle); + let elapsed = process.start.elapsed().unwrap_or(Duration::ZERO); + // If we started and ended really quickly, something might've gone wrong + // Or if the status isn't 0 + // Or if it's an error + if !process.manually_killed + && (elapsed.as_secs() <= 2 || result.is_err() || !result.unwrap().success()) + { + warn!("drop detected that the game {game_id} may have failed to launch properly"); + let _ = self.app_handle.emit("launch_external_error", &game_id); + } + let status = GameStatusManager::fetch_state(&game_id); - push_game_update(&self.app_handle, &game_id, None, status); - - // TODO better management } pub fn valid_platform(&self, platform: &Platform) -> Result { @@ -156,7 +182,7 @@ impl ProcessManager<'_> { { Some(GameDownloadStatus::Installed { version_name, .. }) => version_name, Some(GameDownloadStatus::SetupRequired { .. }) => { - return Err(ProcessError::SetupRequired) + return Err(ProcessError::SetupRequired); } _ => return Err(ProcessError::NotInstalled), }; @@ -202,18 +228,17 @@ impl ProcessManager<'_> { .get(version_name) .ok_or(ProcessError::InvalidVersion)?; + // TODO: refactor this path with open_process_logs + let game_log_folder = &self.log_output_dir.join(game_id); + create_dir_all(game_log_folder).map_err(ProcessError::IOError)?; + let current_time = chrono::offset::Local::now(); let log_file = OpenOptions::new() .write(true) .truncate(true) .read(true) .create(true) - .open(self.log_output_dir.join(format!( - "{}-{}-{}.log", - &game_id, - &version, - current_time.timestamp() - ))) + .open(game_log_folder.join(format!("{}-{}.log", &version, current_time.timestamp()))) .map_err(ProcessError::IOError)?; let error_file = OpenOptions::new() @@ -221,9 +246,8 @@ impl ProcessManager<'_> { .truncate(true) .read(true) .create(true) - .open(self.log_output_dir.join(format!( - "{}-{}-{}-error.log", - &game_id, + .open(game_log_folder.join(format!( + "{}-{}-error.log", &version, current_time.timestamp() ))) @@ -282,7 +306,7 @@ impl ProcessManager<'_> { #[cfg(unix)] let mut command: Command = Command::new("sh"); #[cfg(unix)] - command.arg("-c").arg(launch_string); + command.args(vec!["-c", &launch_string]); command .stderr(error_file) @@ -325,7 +349,14 @@ impl ProcessManager<'_> { drop(app_state_handle); }); - self.processes.insert(meta.id, wait_thread_handle); + self.processes.insert( + meta.id, + RunningProcess { + handle: wait_thread_handle, + start: SystemTime::now(), + manually_killed: false, + }, + ); Ok(()) } } diff --git a/src-tauri/src/remote/cache.rs b/src-tauri/src/remote/cache.rs index 892ce27..9b73df5 100644 --- a/src-tauri/src/remote/cache.rs +++ b/src-tauri/src/remote/cache.rs @@ -1,11 +1,15 @@ +use std::{ + fmt::Display, + time::{Duration, SystemTime}, +}; + use crate::{ database::{db::borrow_db_checked, models::data::Database}, error::remote_access_error::RemoteAccessError, }; +use bitcode::{Decode, DecodeOwned, Encode}; use cacache::Integrity; -use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder, Response}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_binary::binary_stream::Endian; +use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder}; #[macro_export] macro_rules! offline { @@ -19,31 +23,48 @@ macro_rules! offline { } } -pub fn cache_object, D: Serialize + DeserializeOwned>( +pub fn cache_object, D: Encode>( key: K, data: &D, ) -> Result { - let bytes = serde_binary::to_vec(data, Endian::Little).unwrap(); + let bytes = bitcode::encode(data); cacache::write_sync(&borrow_db_checked().cache_dir, key, bytes) .map_err(RemoteAccessError::Cache) } -pub fn get_cached_object, D: Serialize + DeserializeOwned>( +pub fn get_cached_object + Display, D: Encode + DecodeOwned>( key: K, ) -> Result { get_cached_object_db::(key, &borrow_db_checked()) } -pub fn get_cached_object_db, D: Serialize + DeserializeOwned>( +pub fn get_cached_object_db + Display, D: DecodeOwned>( key: K, db: &Database, ) -> Result { - let bytes = cacache::read_sync(&db.cache_dir, key).map_err(RemoteAccessError::Cache)?; - let data = serde_binary::from_slice::(&bytes, Endian::Little).unwrap(); + let bytes = cacache::read_sync(&db.cache_dir, &key).map_err(RemoteAccessError::Cache)?; + let data = bitcode::decode::(&bytes).map_err(|_| { + RemoteAccessError::Cache(cacache::Error::EntryNotFound( + db.cache_dir.clone(), + key.to_string(), + )) + })?; Ok(data) } -#[derive(Serialize, Deserialize)] +#[derive(Encode, Decode)] pub struct ObjectCache { content_type: String, body: Vec, + expiry: u128, +} + +impl ObjectCache { + pub fn has_expired(&self) -> bool { + let duration = Duration::from_millis(self.expiry.try_into().unwrap()); + SystemTime::UNIX_EPOCH + .checked_add(duration) + .unwrap() + .elapsed() + .is_err() + } } impl From>> for ObjectCache { @@ -57,6 +78,12 @@ impl From>> for ObjectCache { .unwrap() .to_owned(), body: value.body().clone(), + expiry: SystemTime::now() + .checked_add(Duration::from_days(1)) + .unwrap() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis(), } } } @@ -66,3 +93,9 @@ impl From for Response> { resp_builder.body(value.body).unwrap() } } +impl From<&ObjectCache> for Response> { + fn from(value: &ObjectCache) -> Self { + let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type.clone()); + resp_builder.body(value.body.clone()).unwrap() + } +} diff --git a/src-tauri/src/remote/fetch_object.rs b/src-tauri/src/remote/fetch_object.rs index bd3a581..fbd3ac4 100644 --- a/src-tauri/src/remote/fetch_object.rs +++ b/src-tauri/src/remote/fetch_object.rs @@ -12,6 +12,14 @@ pub fn fetch_object(request: http::Request>, responder: UriSchemeRespond // Drop leading / let object_id = &request.uri().path()[1..]; + let cache_result = get_cached_object::<&str, ObjectCache>(object_id); + if let Ok(cache_result) = &cache_result + && !cache_result.has_expired() + { + responder.respond(cache_result.into()); + return; + } + let header = generate_authorization_header(); let client: reqwest::blocking::Client = reqwest::blocking::Client::new(); let response = make_request(&client, &["/api/v1/client/object/", object_id], &[], |f| { @@ -20,10 +28,8 @@ pub fn fetch_object(request: http::Request>, responder: UriSchemeRespond .unwrap() .send(); if response.is_err() { - let data = get_cached_object::<&str, ObjectCache>(object_id); - - match data { - Ok(data) => responder.respond(data.into()), + match cache_result { + Ok(cache_result) => responder.respond(cache_result.into()), Err(e) => { warn!("{e}") } @@ -38,7 +44,9 @@ pub fn fetch_object(request: http::Request>, responder: UriSchemeRespond ); let data = Vec::from(response.bytes().unwrap()); let resp = resp_builder.body(data).unwrap(); - cache_object::<&str, ObjectCache>(object_id, &resp.clone().into()).unwrap(); + if cache_result.is_err() || cache_result.unwrap().has_expired() { + cache_object::<&str, ObjectCache>(object_id, &resp.clone().into()).unwrap(); + } responder.respond(resp); } diff --git a/src-tauri/src/remote/mod.rs b/src-tauri/src/remote/mod.rs index 59893eb..c5e1843 100644 --- a/src-tauri/src/remote/mod.rs +++ b/src-tauri/src/remote/mod.rs @@ -3,6 +3,6 @@ pub mod auth; pub mod cache; pub mod commands; pub mod fetch_object; -pub mod utils; pub mod requests; pub mod server_proto; +pub mod utils; diff --git a/src-tauri/src/remote/server_proto.rs b/src-tauri/src/remote/server_proto.rs index 4c5b889..26c0e15 100644 --- a/src-tauri/src/remote/server_proto.rs +++ b/src-tauri/src/remote/server_proto.rs @@ -33,10 +33,7 @@ pub fn handle_server_proto(request: Request>, responder: UriSchemeRespon let whitelist_prefix = ["/store", "/api", "/_", "/fonts"]; - if whitelist_prefix - .iter() - .all(|f| !path.starts_with(f)) - { + if whitelist_prefix.iter().all(|f| !path.starts_with(f)) { webbrowser::open(&new_uri.to_string()).unwrap(); return; } diff --git a/yarn.lock b/yarn.lock index b1299dd..ea3c8db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1508,6 +1508,11 @@ resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.1.tgz#dc49d899fb873b96ee1d46a171384625ba5ad404" integrity sha512-eoQWT+Tq1qSwQpHV+nw1eNYe5B/nm1PoRjQCRiEOS12I1b+X4PUcREfXVX8dPcBT6GrzWGDtaecY0+1p0Rfqlw== +"@tauri-apps/api@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.6.0.tgz#efd873bf04b0d72cea81f9397e16218f5deafe0f" + integrity sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg== + "@tauri-apps/cli-darwin-arm64@2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.1.tgz#5816c0099977f705d1a7249822fa51f5d3c3750a" @@ -1588,6 +1593,13 @@ dependencies: "@tauri-apps/api" "^2.0.0" +"@tauri-apps/plugin-opener@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-opener/-/plugin-opener-2.4.0.tgz#57eae5998e1c396791af16832a9dde16eca06439" + integrity sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ== + dependencies: + "@tauri-apps/api" "^2.6.0" + "@tauri-apps/plugin-os@~2": version "2.2.0" resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.2.0.tgz#ef5511269f59c0ccc580a9d09600034cfaa9743b"