Compare commits
2 Commits
c00b7dbe9c
...
2b64d1e4ba
Author | SHA1 | Date | |
---|---|---|---|
2b64d1e4ba | |||
942eb8003e |
571
Cargo.lock
generated
571
Cargo.lock
generated
@ -163,6 +163,146 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d43c03d9e36dd40cab48435be0b09646da362c278223ca535493877b2c1dee9"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling 3.7.4",
|
||||
"rustix",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.0"
|
||||
@ -342,6 +482,19 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.3.0"
|
||||
@ -940,6 +1093,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.18"
|
||||
@ -1003,6 +1167,15 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2"
|
||||
version = "0.7.0"
|
||||
@ -1032,6 +1205,12 @@ version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.1"
|
||||
@ -1106,6 +1285,33 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.2"
|
||||
@ -1164,6 +1370,16 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
@ -1357,6 +1573,19 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
@ -1755,6 +1984,12 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@ -2378,6 +2613,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2393,8 +2629,10 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-store",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@ -2588,7 +2826,7 @@ dependencies = [
|
||||
"flume",
|
||||
"if-addrs",
|
||||
"log",
|
||||
"polling",
|
||||
"polling 2.8.0",
|
||||
"socket2",
|
||||
]
|
||||
|
||||
@ -2635,7 +2873,7 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
@ -2703,6 +2941,18 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
@ -2940,6 +3190,7 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block2",
|
||||
"dispatch",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
@ -3081,6 +3332,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.1"
|
||||
@ -3324,6 +3585,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
@ -3359,7 +3631,7 @@ checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.6.0",
|
||||
"quick-xml",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@ -3393,6 +3665,21 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi 0.4.0",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@ -3512,6 +3799,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
@ -3724,6 +4020,29 @@ dependencies = [
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@ -4089,6 +4408,12 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@ -4673,6 +4998,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.7"
|
||||
@ -4992,6 +5323,45 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4307310e1d2c09ab110235834722e7c2b85099b683e1eb7342ab351b0be5ada3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 1.0.68",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96ba7d46e86db8c830d143ef90ab5a453328365b0cc834c24edea4267b16aba0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 1.0.68",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.0.2"
|
||||
@ -5035,6 +5405,22 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-store"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9a580be53f04bb62422d239aa798e88522877f58a0d4a0e745f030055a51bb4"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 1.0.68",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.2.0"
|
||||
@ -5280,6 +5666,7 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@ -5484,6 +5871,17 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
@ -5807,6 +6205,66 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.36.2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.72"
|
||||
@ -6375,6 +6833,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.4"
|
||||
@ -6399,6 +6867,65 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b8e3d6ae3342792a6cc2340e4394334c7402f3d793b390d2c5494a4032b3030"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"derivative",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7a3e850ff1e7217a3b7a07eba90d37fe9bb9e89a310f718afcde5885ca9b6d7"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
@ -6482,3 +7009,41 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e09e8be97d44eeab994d752f341e67b3b0d80512a8b315a0671d47232ef1b65"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"url",
|
||||
"zvariant_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a5857e2856435331636a9fbb415b09243df4521a267c5bedcd5289b4d5799e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00bedb16a193cc12451873fee2a1bc6550225acece0e36f333e68326c73c8172"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
@ -23,6 +23,7 @@ serde_json = "1.0"
|
||||
tokio = { version = "1.41", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
walkdir = "2.5"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
@ -1,27 +1,138 @@
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::{fs::File, io::Write as _, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use lanspread_db::db::Game;
|
||||
use lanspread_db::db::{Game, GameFileDescription};
|
||||
use lanspread_proto::{Message as _, Request, Response};
|
||||
use lanspread_utils::maybe_addr;
|
||||
use s2n_quic::{client::Connect, provider::limits::Limits, Client as QuicClient};
|
||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use s2n_quic::{client::Connect, provider::limits::Limits, Client as QuicClient, Connection};
|
||||
use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
stream,
|
||||
sync::{
|
||||
mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
static CERT_PEM: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../cert.pem"));
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientEvent {
|
||||
ListGames(Vec<Game>),
|
||||
GotGameFiles(Vec<GameFileDescription>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientCommand {
|
||||
ListGames,
|
||||
GetGame(String),
|
||||
DownloadGameFiles(Vec<GameFileDescription>),
|
||||
ServerAddr(SocketAddr),
|
||||
SetGameDir(String),
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
async fn initial_server_alive_check(conn: &mut Connection) -> bool {
|
||||
let stream = match conn.open_bidirectional_stream().await {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
log::error!("failed to open stream: {e}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let (mut rx, mut tx) = stream.split();
|
||||
|
||||
// send ping
|
||||
if let Err(e) = tx.send(Request::Ping.encode()).await {
|
||||
log::error!("failed to send ping to server: {e}");
|
||||
return false;
|
||||
}
|
||||
let _ = tx.close().await;
|
||||
|
||||
// receive pong
|
||||
if let Ok(Some(response)) = rx.receive().await {
|
||||
let response = Response::decode(response);
|
||||
if let Response::Pong = response {
|
||||
log::info!("server is alive");
|
||||
return true;
|
||||
}
|
||||
|
||||
log::error!("server sent invalid response to ping: {response:?}");
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
async fn download_game_files(
|
||||
game_file_descs: Vec<GameFileDescription>,
|
||||
games_dir: String,
|
||||
server_addr: SocketAddr,
|
||||
) -> eyre::Result<()> {
|
||||
let limits = Limits::default()
|
||||
.with_max_handshake_duration(Duration::from_secs(3))?
|
||||
.with_max_idle_timeout(Duration::from_secs(1))?;
|
||||
|
||||
let client = QuicClient::builder()
|
||||
.with_tls(CERT_PEM)?
|
||||
.with_io("0.0.0.0:0")?
|
||||
.with_limits(limits)?
|
||||
.start()?;
|
||||
|
||||
let conn = Connect::new(server_addr).with_server_name("localhost");
|
||||
let mut conn = client.connect(conn).await?;
|
||||
conn.keep_alive(true)?;
|
||||
|
||||
let game_file_descs = game_file_descs
|
||||
.into_iter()
|
||||
.filter(|desc| !desc.is_dir)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for file_desc in game_file_descs {
|
||||
log::info!("downloading file: {}", file_desc.relative_path);
|
||||
|
||||
let stream = conn.open_bidirectional_stream().await?;
|
||||
let (mut rx, mut tx) = stream.split();
|
||||
|
||||
let request = Request::GetGameFileData(file_desc.clone());
|
||||
|
||||
if let Ok(()) = tx.write_all(&request.encode()).await {
|
||||
let path = PathBuf::from(&games_dir).join(&file_desc.relative_path);
|
||||
let mut file = match File::create(&path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
log::error!("failed to create file: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// if let Err(e) = tokio::io::copy(&mut rx, &mut file).await {
|
||||
// log::error!("failed to download file: {e}");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
while let Ok(Some(data)) = rx.receive().await {
|
||||
if let Err(e) = file.write_all(&data) {
|
||||
log::error!("failed to write to file: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
log::error!("file download complete: {}", path.display());
|
||||
}
|
||||
if let Err(e) = tx.close().await {
|
||||
log::error!("failed to close stream: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Ctx {
|
||||
game_dir: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn run(
|
||||
mut rx_control: UnboundedReceiver<ClientCommand>,
|
||||
tx_notify_ui: UnboundedSender<ClientEvent>,
|
||||
@ -35,6 +146,11 @@ pub async fn run(
|
||||
}
|
||||
};
|
||||
|
||||
// client context
|
||||
let ctx = Ctx {
|
||||
game_dir: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
loop {
|
||||
let limits = Limits::default()
|
||||
.with_max_handshake_duration(Duration::from_secs(3))?
|
||||
@ -58,6 +174,10 @@ pub async fn run(
|
||||
|
||||
conn.keep_alive(true)?;
|
||||
|
||||
if !initial_server_alive_check(&mut conn).await {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"connected: (server: {}) (client: {})",
|
||||
maybe_addr!(conn.remote_addr()),
|
||||
@ -68,11 +188,36 @@ pub async fn run(
|
||||
while let Some(cmd) = rx_control.recv().await {
|
||||
let request = match cmd {
|
||||
ClientCommand::ListGames => Request::ListGames,
|
||||
ClientCommand::GetGame(id) => Request::GetGame { id },
|
||||
ClientCommand::ServerAddr(_) => Request::Invalid(
|
||||
Bytes::new(),
|
||||
"invalid control message (ServerAddr), should not happen".into(),
|
||||
),
|
||||
ClientCommand::GetGame(id) => {
|
||||
log::debug!("requesting game from server: {id}");
|
||||
Request::GetGame { id }
|
||||
}
|
||||
ClientCommand::ServerAddr(_) => {
|
||||
log::warn!("unexpected ServerAddr command from UI client");
|
||||
continue;
|
||||
}
|
||||
ClientCommand::SetGameDir(game_dir) => {
|
||||
*ctx.game_dir.lock().await = Some(game_dir.clone());
|
||||
continue;
|
||||
}
|
||||
ClientCommand::DownloadGameFiles(game_file_descs) => {
|
||||
log::info!("got ClientCommand::DownloadGameFiles");
|
||||
|
||||
let games_dir = { ctx.game_dir.lock().await.clone() };
|
||||
if let Some(games_dir) = games_dir {
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(e) =
|
||||
download_game_files(game_file_descs, games_dir, server_addr).await
|
||||
{
|
||||
log::error!("failed to download game files: {e}");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log::error!("Cannot handle game file descriptions: game_dir is not set");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let data = request.encode();
|
||||
@ -114,7 +259,37 @@ pub async fn run(
|
||||
log::info!("sent ClientEvent::ListGames to Tauri client");
|
||||
}
|
||||
}
|
||||
Response::Game(game) => log::debug!("game received: {game:?}"),
|
||||
Response::GetGame(game_file_descs) => {
|
||||
log::info!(
|
||||
"got {} game file descriptions from server",
|
||||
game_file_descs.len()
|
||||
);
|
||||
|
||||
let games_dir = { ctx.game_dir.lock().await.clone() };
|
||||
|
||||
match games_dir {
|
||||
Some(games_dir) => {
|
||||
game_file_descs.iter().filter(|f| f.is_dir).for_each(|dir| {
|
||||
let path = PathBuf::from(&games_dir).join(&dir.relative_path);
|
||||
if let Err(e) = std::fs::create_dir_all(path) {
|
||||
log::error!("failed to create directory: {e}");
|
||||
}
|
||||
});
|
||||
if let Err(e) =
|
||||
tx_notify_ui.send(ClientEvent::GotGameFiles(game_file_descs))
|
||||
{
|
||||
log::error!(
|
||||
"failed to send ClientEvent::GotGameFiles to client: {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::error!(
|
||||
"Cannot handle game file descriptions: game_dir is not set"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Response::GameNotFound(id) => log::debug!("game not found {id}"),
|
||||
Response::InvalidRequest(request_bytes, err) => log::error!(
|
||||
"server says our request was invalid (error: {}): {}",
|
||||
@ -131,6 +306,7 @@ pub async fn run(
|
||||
String::from_utf8_lossy(&data)
|
||||
);
|
||||
}
|
||||
Response::Pong => (), // ignore (should never happen)
|
||||
}
|
||||
|
||||
if let Err(err) = tx.close().await {
|
||||
|
@ -29,6 +29,8 @@ pub struct Game {
|
||||
pub size: u64,
|
||||
/// thumbnail image
|
||||
pub thumbnail: Option<Bytes>,
|
||||
/// only relevant for client (yeah... I know)
|
||||
pub installed: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Game {
|
||||
@ -76,7 +78,7 @@ pub struct GameDB {
|
||||
|
||||
impl GameDB {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
pub fn empty() -> Self {
|
||||
GameDB {
|
||||
games: HashMap::new(),
|
||||
}
|
||||
@ -84,7 +86,7 @@ impl GameDB {
|
||||
|
||||
#[must_use]
|
||||
pub fn from(games: Vec<Game>) -> Self {
|
||||
let mut db = GameDB::new();
|
||||
let mut db = GameDB::empty();
|
||||
for game in games {
|
||||
db.games.insert(game.id.clone(), game);
|
||||
}
|
||||
@ -99,6 +101,14 @@ impl GameDB {
|
||||
self.games.get(id.as_ref())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_mut_game_by_id<S>(&mut self, id: S) -> Option<&mut Game>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.games.get_mut(id.as_ref())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_game_by_name(&self, name: &str) -> Option<&Game> {
|
||||
self.games.values().find(|game| game.name == name)
|
||||
@ -110,10 +120,34 @@ impl GameDB {
|
||||
games.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
games
|
||||
}
|
||||
|
||||
pub fn set_all_uninstalled(&mut self) {
|
||||
for game in self.games.values_mut() {
|
||||
game.installed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GameDB {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct GameFileDescription {
|
||||
pub game_id: String,
|
||||
pub relative_path: String,
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for GameFileDescription {
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"game:{} path:{} dir:{}",
|
||||
self.game_id, self.relative_path, self.is_dir
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,22 @@
|
||||
use bytes::Bytes;
|
||||
use lanspread_db::db::Game;
|
||||
use lanspread_db::db::{Game, GameFileDescription};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Request {
|
||||
Ping,
|
||||
ListGames,
|
||||
GetGame { id: String },
|
||||
GetGameFileData(GameFileDescription),
|
||||
Invalid(Bytes, String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
Pong,
|
||||
Games(Vec<Game>),
|
||||
Game(Game),
|
||||
GetGame(Vec<GameFileDescription>),
|
||||
GameNotFound(String),
|
||||
InvalidRequest(Bytes, String),
|
||||
EncodingError(String),
|
||||
@ -72,16 +75,3 @@ impl Message for Response {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods for Response
|
||||
impl Response {
|
||||
#[must_use]
|
||||
pub fn games(games: Vec<Game>) -> Self {
|
||||
Response::Games(games)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn game(game: Game) -> Self {
|
||||
Response::Game(game)
|
||||
}
|
||||
}
|
||||
|
@ -30,3 +30,4 @@ semver = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
|
@ -1,15 +1,18 @@
|
||||
#![allow(clippy::doc_markdown)]
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read as _,
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use assets::Thumbnails;
|
||||
use bytes::Bytes;
|
||||
use clap::Parser;
|
||||
use lanspread_compat::eti::{self, EtiGame};
|
||||
use lanspread_db::db::{Game, GameDB};
|
||||
use lanspread_db::db::{Game, GameDB, GameFileDescription};
|
||||
use lanspread_mdns::{
|
||||
DaemonEvent,
|
||||
MdnsAdvertiser,
|
||||
@ -21,6 +24,7 @@ use lanspread_utils::maybe_addr;
|
||||
use s2n_quic::Server as QuicServer;
|
||||
use tokio::{io::AsyncWriteExt, sync::Mutex};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
mod assets;
|
||||
|
||||
@ -30,6 +34,7 @@ static CERT_PEM: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../..
|
||||
#[derive(Clone, Debug)]
|
||||
struct ServerCtx {
|
||||
handler: RequestHandler,
|
||||
games_folder: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -38,7 +43,7 @@ struct ConnectionCtx {
|
||||
remote_addr: String,
|
||||
}
|
||||
|
||||
async fn run(addr: SocketAddr, db: GameDB) -> eyre::Result<()> {
|
||||
async fn run(addr: SocketAddr, db: GameDB, games_folder: PathBuf) -> eyre::Result<()> {
|
||||
let mut server = QuicServer::builder()
|
||||
.with_tls((CERT_PEM, KEY_PEM))?
|
||||
.with_io(addr)?
|
||||
@ -46,6 +51,7 @@ async fn run(addr: SocketAddr, db: GameDB) -> eyre::Result<()> {
|
||||
|
||||
let server_ctx = Arc::new(ServerCtx {
|
||||
handler: RequestHandler::new(db),
|
||||
games_folder,
|
||||
});
|
||||
|
||||
while let Some(mut connection) = server.accept().await {
|
||||
@ -75,7 +81,69 @@ async fn run(addr: SocketAddr, db: GameDB) -> eyre::Result<()> {
|
||||
|
||||
let request = Request::decode(data);
|
||||
tracing::debug!("{} msg: {:?}", conn_ctx.remote_addr, request);
|
||||
let response = conn_ctx.server_ctx.handler.handle_request(request).await;
|
||||
|
||||
if let Request::GetGameFileData(game_file_desc) = &request {
|
||||
tracing::debug!(
|
||||
"{} client requested game file data: {:?}",
|
||||
conn_ctx.remote_addr,
|
||||
game_file_desc
|
||||
);
|
||||
|
||||
// deliver file data to client
|
||||
let path = conn_ctx
|
||||
.server_ctx
|
||||
.games_folder
|
||||
.join(&game_file_desc.relative_path);
|
||||
|
||||
if let Ok(mut file) = File::open(&path) {
|
||||
let mut buf = vec![0; 64 * 1024];
|
||||
|
||||
while let Ok(n) = file.read(&mut buf) {
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Err(e) = tx.write_all(&buf[..n]).await {
|
||||
tracing::error!(
|
||||
"{} failed to send file data: {}",
|
||||
conn_ctx.remote_addr,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if let Err(e) = tokio::io::copy(&mut file, &mut tx).await {
|
||||
// tracing::error!(
|
||||
// "{} failed to send file data: {}",
|
||||
// conn_ctx.remote_addr,
|
||||
// e
|
||||
// );
|
||||
// }
|
||||
} else {
|
||||
tracing::error!(
|
||||
"{} failed to open file: {}",
|
||||
conn_ctx.remote_addr,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = tx.close().await {
|
||||
tracing::error!(
|
||||
"{} failed to close stream: {}",
|
||||
conn_ctx.remote_addr,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let response = conn_ctx
|
||||
.server_ctx
|
||||
.handler
|
||||
.handle_request(request, &conn_ctx)
|
||||
.await;
|
||||
|
||||
tracing::trace!("{} server response: {:?}", conn_ctx.remote_addr, response);
|
||||
let raw_response = response.encode();
|
||||
tracing::trace!(
|
||||
@ -102,6 +170,21 @@ async fn run(addr: SocketAddr, db: GameDB) -> eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_relative_path(base: &Path, deep_path: &Path) -> std::io::Result<PathBuf> {
|
||||
let base_canonical = base.canonicalize()?;
|
||||
let full_canonical = deep_path.canonicalize()?;
|
||||
|
||||
full_canonical
|
||||
.strip_prefix(&base_canonical)
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Path is not within base directory",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RequestHandler {
|
||||
db: Arc<Mutex<GameDB>>,
|
||||
@ -114,18 +197,60 @@ impl RequestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(&self, request: Request) -> Response {
|
||||
async fn handle_request(&self, request: Request, conn_ctx: &ConnectionCtx) -> Response {
|
||||
match request {
|
||||
Request::Ping => Response::Pong,
|
||||
Request::ListGames => {
|
||||
let db = self.db.lock().await;
|
||||
Response::Games(db.all_games().into_iter().cloned().collect())
|
||||
}
|
||||
Request::GetGame { id } => {
|
||||
let db = self.db.lock().await;
|
||||
match db.get_game_by_id(&id) {
|
||||
Some(game) => Response::Game(game.clone()),
|
||||
None => Response::GameNotFound(id),
|
||||
if self.db.lock().await.get_game_by_id(&id).is_none() {
|
||||
tracing::error!("Game not found in DB: {id}");
|
||||
return Response::GameNotFound(id);
|
||||
}
|
||||
|
||||
let games_folder = &conn_ctx.server_ctx.games_folder;
|
||||
|
||||
let game_dir = games_folder.join(&id);
|
||||
if !game_dir.exists() {
|
||||
tracing::error!("Game folder does not exist: {}", game_dir.display());
|
||||
return Response::GameNotFound(id);
|
||||
}
|
||||
|
||||
let mut game_files_descs: Vec<GameFileDescription> = vec![];
|
||||
|
||||
for entry in WalkDir::new(&game_dir)
|
||||
.into_iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
{
|
||||
match get_relative_path(games_folder, entry.path()) {
|
||||
Ok(relative_path) => match relative_path.to_str() {
|
||||
Some(relative_path) => {
|
||||
let game_file_description = GameFileDescription {
|
||||
game_id: id.clone(),
|
||||
relative_path: relative_path.to_string(),
|
||||
is_dir: entry.file_type().is_dir(),
|
||||
};
|
||||
|
||||
tracing::debug!("Found game file: {:?}", game_file_description);
|
||||
|
||||
game_files_descs.push(game_file_description);
|
||||
}
|
||||
None => {
|
||||
tracing::error!("Failed to get relative path: {relative_path:?}",);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to get relative path: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Response::GetGame(game_files_descs)
|
||||
}
|
||||
Request::GetGameFileData(_) => {
|
||||
Response::InvalidRequest(Bytes::new(), "Not implemented".to_string())
|
||||
}
|
||||
Request::Invalid(data, err_msg) => {
|
||||
tracing::error!(
|
||||
@ -172,6 +297,7 @@ fn eti_game_to_game(eti_game: EtiGame) -> Game {
|
||||
genre: eti_game.genre_de,
|
||||
size: (eti_game.game_size * 1024.0 * 1024.0 * 1024.0) as u64,
|
||||
thumbnail: None,
|
||||
installed: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,6 +309,14 @@ async fn main() -> eyre::Result<()> {
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
#[allow(clippy::manual_assert)]
|
||||
if !cli.folder.exists() {
|
||||
panic!(
|
||||
"Games folder does not exist: {}",
|
||||
cli.folder.to_str().expect("Invalid path")
|
||||
);
|
||||
}
|
||||
|
||||
let eti_games = eti::get_games(&cli.db).await?;
|
||||
let mut games: Vec<Game> = eti_games.into_iter().map(eti_game_to_game).collect();
|
||||
let thumbnails = Thumbnails::new(cli.thumbnails);
|
||||
@ -216,5 +350,5 @@ async fn main() -> eyre::Result<()> {
|
||||
|
||||
tracing::info!("Server listening on {}:{}", cli.ip, cli.port);
|
||||
|
||||
run(SocketAddr::from((cli.ip, cli.port)), game_db).await
|
||||
run(SocketAddr::from((cli.ip, cli.port)), game_db, cli.folder).await
|
||||
}
|
||||
|
16
crates/lanspread-tauri-deno-ts/deno.lock
generated
16
crates/lanspread-tauri-deno-ts/deno.lock
generated
@ -3,7 +3,9 @@
|
||||
"specifiers": {
|
||||
"npm:@tauri-apps/api@2": "2.1.1",
|
||||
"npm:@tauri-apps/cli@2": "2.1.0",
|
||||
"npm:@tauri-apps/plugin-dialog@~2.0.1": "2.0.1",
|
||||
"npm:@tauri-apps/plugin-shell@2": "2.0.1",
|
||||
"npm:@tauri-apps/plugin-store@2.1": "2.1.0",
|
||||
"npm:@types/react-dom@^18.2.7": "18.3.1",
|
||||
"npm:@types/react@^18.2.15": "18.3.12",
|
||||
"npm:@vitejs/plugin-react@^4.2.1": "4.3.3_vite@5.4.11_@babel+core@7.26.0",
|
||||
@ -348,12 +350,24 @@
|
||||
"@tauri-apps/cli-win32-x64-msvc"
|
||||
]
|
||||
},
|
||||
"@tauri-apps/plugin-dialog@2.0.1": {
|
||||
"integrity": "sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==",
|
||||
"dependencies": [
|
||||
"@tauri-apps/api"
|
||||
]
|
||||
},
|
||||
"@tauri-apps/plugin-shell@2.0.1": {
|
||||
"integrity": "sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==",
|
||||
"dependencies": [
|
||||
"@tauri-apps/api"
|
||||
]
|
||||
},
|
||||
"@tauri-apps/plugin-store@2.1.0": {
|
||||
"integrity": "sha512-GADqrc17opUKYIAKnGHIUgEeTZ2wJGu1ZITKQ1WMuOFdv8fvXRFBAqsqPjE3opgWohbczX6e1NpwmZK1AnuWVw==",
|
||||
"dependencies": [
|
||||
"@tauri-apps/api"
|
||||
]
|
||||
},
|
||||
"@types/babel__core@7.20.5": {
|
||||
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
||||
"dependencies": [
|
||||
@ -604,7 +618,9 @@
|
||||
"dependencies": [
|
||||
"npm:@tauri-apps/api@2",
|
||||
"npm:@tauri-apps/cli@2",
|
||||
"npm:@tauri-apps/plugin-dialog@~2.0.1",
|
||||
"npm:@tauri-apps/plugin-shell@2",
|
||||
"npm:@tauri-apps/plugin-store@2.1",
|
||||
"npm:@types/react-dom@^18.2.7",
|
||||
"npm:@types/react@^18.2.15",
|
||||
"npm:@vitejs/plugin-react@^4.2.1",
|
||||
|
@ -10,6 +10,8 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/plugin-dialog": "~2.0.1",
|
||||
"@tauri-apps/plugin-store": "~2.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
|
@ -32,4 +32,6 @@ tauri-plugin-shell = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { workspace = true }
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-store = "2"
|
||||
|
||||
|
@ -2,9 +2,13 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"shell:allow-open"
|
||||
"shell:allow-open",
|
||||
"dialog:default",
|
||||
"store:default"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use lanspread_client::{ClientCommand, ClientEvent};
|
||||
use lanspread_db::db::{Game, GameDB};
|
||||
use lanspread_mdns::{discover_service, LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE};
|
||||
use tauri::{AppHandle, Emitter as _, Listener as _, Manager};
|
||||
use tauri::{AppHandle, Emitter as _, Manager};
|
||||
use tokio::sync::{mpsc::UnboundedSender, Mutex};
|
||||
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
@ -10,6 +15,7 @@ use tokio::sync::{mpsc::UnboundedSender, Mutex};
|
||||
struct LanSpreadState {
|
||||
server_addr: Mutex<Option<SocketAddr>>,
|
||||
client_ctrl: UnboundedSender<ClientCommand>,
|
||||
games: Arc<Mutex<GameDB>>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -22,7 +28,7 @@ fn request_games(state: tauri::State<LanSpreadState>) {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn run_game_backend(id: String, state: tauri::State<LanSpreadState>) -> String {
|
||||
fn install_game(id: String, state: tauri::State<LanSpreadState>) -> String {
|
||||
log::error!("Running game with id {id}");
|
||||
|
||||
// let result = Command::new(r#"C:\Users\ddidderr\scoop\apps\mpv\0.39.0\mpv.exe"#).spawn();
|
||||
@ -40,6 +46,73 @@ fn run_game_backend(id: String, state: tauri::State<LanSpreadState>) -> String {
|
||||
// }
|
||||
}
|
||||
|
||||
fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) {
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if let Some(file_name) = file_name.to_str() {
|
||||
if let Some(game) = game_db.get_mut_game_by_id(file_name) {
|
||||
if installed {
|
||||
log::error!("Game is installed: {game}");
|
||||
} else {
|
||||
log::error!("Game is missing: {game}");
|
||||
}
|
||||
game.installed = installed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
|
||||
app_handle
|
||||
.state::<LanSpreadState>()
|
||||
.client_ctrl
|
||||
.send(ClientCommand::SetGameDir(path.clone()))
|
||||
.unwrap();
|
||||
|
||||
let path = PathBuf::from(path);
|
||||
if !path.exists() {
|
||||
log::error!("game dir {path:?} does not exist");
|
||||
}
|
||||
|
||||
let entries = match path.read_dir() {
|
||||
Ok(entries) => entries,
|
||||
Err(e) => {
|
||||
log::error!("Failed to read game dir: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut game_db = app_handle
|
||||
.state::<LanSpreadState>()
|
||||
.inner()
|
||||
.games
|
||||
.lock()
|
||||
.await;
|
||||
|
||||
// Reset all games to uninstalled
|
||||
game_db.set_all_uninstalled();
|
||||
|
||||
// update game_db with installed games from real game directory
|
||||
entries.into_iter().for_each(|entry| {
|
||||
if let Ok(entry) = entry {
|
||||
if let Ok(path_type) = entry.file_type() {
|
||||
if path_type.is_dir() {
|
||||
let path = entry.path();
|
||||
if path.join(".softlan_game_installed").exists() {
|
||||
set_game_install_state_from_path(&mut game_db, &path, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = app_handle.emit("games-list-updated", Some(game_db.all_games())) {
|
||||
log::error!("Failed to emit games-list-updated event: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn find_server(app: AppHandle) {
|
||||
log::info!("Looking for server...");
|
||||
|
||||
@ -57,6 +130,7 @@ async fn find_server(app: AppHandle) {
|
||||
.client_ctrl
|
||||
.send(ClientCommand::ServerAddr(server_addr))
|
||||
.unwrap();
|
||||
request_games(state);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
@ -66,6 +140,25 @@ async fn find_server(app: AppHandle) {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_game_db(games: Vec<Game>, app: AppHandle) {
|
||||
for game in &games {
|
||||
log::trace!("client event ListGames iter: {game:?}");
|
||||
}
|
||||
|
||||
let state = app.state::<LanSpreadState>();
|
||||
|
||||
// Store games list
|
||||
let mut state_games = state.games.lock().await;
|
||||
*state_games = GameDB::from(games.clone());
|
||||
|
||||
// Tell Frontend about new games list
|
||||
if let Err(e) = app.emit("games-list-updated", Some(games)) {
|
||||
log::error!("Failed to emit games-list-updated event: {e}");
|
||||
} else {
|
||||
log::info!("Emitted games-list-updated event");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let tauri_logger_builder = tauri_plugin_log::Builder::new()
|
||||
@ -89,12 +182,19 @@ pub fn run() {
|
||||
let lanspread_state = LanSpreadState {
|
||||
server_addr: Mutex::new(None),
|
||||
client_ctrl: tx_client_control,
|
||||
games: Arc::new(Mutex::new(GameDB::empty())),
|
||||
};
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_logger_builder.build())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.invoke_handler(tauri::generate_handler![run_game_backend, request_games])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
install_game,
|
||||
request_games,
|
||||
update_game_directory
|
||||
])
|
||||
.manage(lanspread_state)
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle().clone();
|
||||
@ -110,16 +210,20 @@ pub fn run() {
|
||||
match event {
|
||||
ClientEvent::ListGames(games) => {
|
||||
log::debug!("Received client event: ListGames");
|
||||
|
||||
for game in &games {
|
||||
log::trace!("client event ListGames iter: {game:?}");
|
||||
update_game_db(games, app_handle.clone()).await;
|
||||
}
|
||||
ClientEvent::GotGameFiles(game_file_descs) => {
|
||||
log::debug!("Received client event: GotGameFiles");
|
||||
if let Err(e) = app_handle.emit("game-download-in-progress", Some(())) {
|
||||
log::error!("Failed to emit game-files event: {e}");
|
||||
}
|
||||
|
||||
if let Err(e) = app_handle.emit("games-list-updated", Some(games)) {
|
||||
log::error!("Failed to emit games-list-updated event: {e}");
|
||||
} else {
|
||||
log::info!("Emitted games-list-updated event");
|
||||
}
|
||||
app_handle
|
||||
.state::<LanSpreadState>()
|
||||
.inner()
|
||||
.client_ctrl
|
||||
.send(ClientCommand::DownloadGameFiles(game_file_descs))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,13 +102,6 @@ h1.align-center {
|
||||
box-shadow: 0 8px 15px rgba(0, 191, 255, 0.2);
|
||||
}
|
||||
|
||||
/* .play-button:hover {
|
||||
background: linear-gradient(45deg, #09305a, #4866b9);
|
||||
/* box-shadow: 0 15px 20px rgba(0, 191, 255, 0.4); */
|
||||
/* box-shadow: 0 20px 25px rgba(0, 191, 255, 0.6), 0 0 15px rgba(0, 191, 255, 0.5);
|
||||
transform: translateY(-5px);
|
||||
} */
|
||||
|
||||
.play-button:hover {
|
||||
background: linear-gradient(45deg, #09305a, #4866b9);
|
||||
box-shadow: 0 20px 25px rgba(0, 191, 255, 0.6);
|
||||
@ -124,7 +117,6 @@ h1.align-center {
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
@ -150,3 +142,45 @@ h1.align-center {
|
||||
.search-input::placeholder {
|
||||
color: #8892b0;
|
||||
}
|
||||
|
||||
.search-settings-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.settings-button {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(45deg, #09305a, #37529c);
|
||||
color: #D5DBFE;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 8px rgba(0, 191, 255, 0.2);
|
||||
}
|
||||
|
||||
.settings-button:hover {
|
||||
background: linear-gradient(45deg, #09305a, #4866b9);
|
||||
box-shadow: 0 8px 12px rgba(0, 191, 255, 0.4);
|
||||
border: 1px solid rgba(0, 191, 255, 0.6);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.settings-text {
|
||||
color: #8892b0;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
@ -1,40 +1,76 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {invoke} from '@tauri-apps/api/core';
|
||||
import {listen} from '@tauri-apps/api/event';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { load } from '@tauri-apps/plugin-store';
|
||||
|
||||
import "./App.css";
|
||||
|
||||
const FILE_STORAGE = 'launcher-settings.json';
|
||||
const GAME_DIR_KEY = 'game-directory';
|
||||
|
||||
interface Game {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
size: number;
|
||||
thumbnail: Uint8Array;
|
||||
installed: boolean;
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const [gameItems, setGameItems] = useState<Game[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [gameDir, setGameDir] = useState('');
|
||||
|
||||
const filteredGames = gameItems.filter(item =>
|
||||
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const getInitialGameDir = async () => {
|
||||
// update game directory from storage (if exists)
|
||||
// only if it's not already set
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const store = await load(FILE_STORAGE, { autoSave: true });
|
||||
const savedGameDir = await store.get<string>(GAME_DIR_KEY);
|
||||
if (savedGameDir) {
|
||||
setGameDir(savedGameDir);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (gameDir) {
|
||||
// store game directory in persistent storage
|
||||
const updateStorage = async (game_dir: string) => {
|
||||
try {
|
||||
const store = await load(FILE_STORAGE, { autoSave: true });
|
||||
await store.set(GAME_DIR_KEY, game_dir);
|
||||
console.info(`📦 Storage updated with game directory: ${game_dir}`);
|
||||
} catch (error) {
|
||||
console.error('❌ Error updating storage:', error);
|
||||
}
|
||||
};
|
||||
|
||||
updateStorage(gameDir);
|
||||
|
||||
console.log(`📂 Game directory changed to: ${gameDir}`);
|
||||
invoke('update_game_directory', { path: gameDir })
|
||||
.catch(error => console.error('❌ Error updating game directory:', error));
|
||||
}
|
||||
}, [gameDir]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🔵 Effect starting - setting up listener and requesting games');
|
||||
|
||||
let isSubscribed = true; // For tracking if component is still mounted
|
||||
|
||||
const setupEventListener = async () => {
|
||||
try {
|
||||
const unlisten = await listen('games-list-updated', (event) => {
|
||||
if (!isSubscribed) return; // Don't update state if unmounted
|
||||
|
||||
console.log('📥 Received games-list-updated event');
|
||||
// Listen for events that update the game list
|
||||
const unlisten_games = await listen('games-list-updated', (event) => {
|
||||
console.log('🗲 Received games-list-updated event');
|
||||
const games = event.payload as Game[];
|
||||
games.forEach(game => {
|
||||
console.log(`🎮 game: ${JSON.stringify(game.id)}`);
|
||||
});
|
||||
console.log(`🎮 ${games.length} Games received`);
|
||||
setGameItems(games);
|
||||
getInitialGameDir();
|
||||
});
|
||||
|
||||
// Initial request for games
|
||||
@ -44,8 +80,7 @@ const App = () => {
|
||||
// Cleanup function
|
||||
return () => {
|
||||
console.log('🧹 Cleaning up - removing listener');
|
||||
isSubscribed = false;
|
||||
unlisten();
|
||||
unlisten_games();
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ Error in setup:', error);
|
||||
@ -57,34 +92,51 @@ const App = () => {
|
||||
// Cleanup
|
||||
return () => {
|
||||
console.log('🚫 Effect cleanup - component unmounting');
|
||||
isSubscribed = false;
|
||||
};
|
||||
}, []); // Empty dependency array means this runs once on mount
|
||||
|
||||
const runGame = async (id: string) => {
|
||||
console.log(`🎯 Running game with id=${id}`);
|
||||
try {
|
||||
const result = await invoke('run_game_backend', {id});
|
||||
const result = await invoke('install_game', {id});
|
||||
console.log(`✅ Game started, result=${result}`);
|
||||
} catch (error) {
|
||||
console.error('❌ Error running game:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const dialogGameDir = async () => {
|
||||
const file = await open({
|
||||
multiple: false,
|
||||
directory: true,
|
||||
});
|
||||
|
||||
if (file) {
|
||||
setGameDir(file);
|
||||
}
|
||||
};
|
||||
|
||||
// Rest of your component remains the same
|
||||
return (
|
||||
<main className="container">
|
||||
<div className="fixed-header">
|
||||
<h1 className="align-center">SoftLAN Launcher</h1>
|
||||
<div className="main-header">
|
||||
<div className="search-container">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search games..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="search-input"
|
||||
/>
|
||||
<div className="search-settings-wrapper">
|
||||
<div></div>
|
||||
<div className="search-container">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search games..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="search-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="settings-container">
|
||||
<button onClick={dialogGameDir} className="settings-button">Set Game Directory</button>
|
||||
<span className="settings-text">{gameDir}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,7 +153,7 @@ const App = () => {
|
||||
<span className="desc-text">{item.description.slice(0, 10)}</span>
|
||||
<span className="size-text">{item.size.toString()}</span>
|
||||
</div>
|
||||
<div className="play-button">Play</div>
|
||||
<div className="play-button">{item.installed ? 'Play' : 'Install'}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
Loading…
x
Reference in New Issue
Block a user