From 942eb8003e4f76f13b0b50586028e370f6f56494 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 14 Nov 2024 19:41:55 +0100 Subject: [PATCH] [improve] set game dir on client -> updates Play/Install button based on games existing --- Cargo.lock | 570 +++++++++++++++++- crates/lanspread-client/src/lib.rs | 43 +- crates/lanspread-db/src/db.rs | 22 +- crates/lanspread-proto/src/lib.rs | 2 + crates/lanspread-server/src/main.rs | 2 + crates/lanspread-tauri-deno-ts/deno.lock | 16 + crates/lanspread-tauri-deno-ts/package.json | 2 + .../src-tauri/Cargo.toml | 2 + .../src-tauri/capabilities/default.json | 10 +- .../src-tauri/src/lib.rs | 111 +++- crates/lanspread-tauri-deno-ts/src/App.css | 50 +- crates/lanspread-tauri-deno-ts/src/App.tsx | 96 ++- 12 files changed, 873 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfdada5..5549b6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -2393,8 +2628,10 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-log", "tauri-plugin-shell", + "tauri-plugin-store", "tokio", ] @@ -2588,7 +2825,7 @@ dependencies = [ "flume", "if-addrs", "log", - "polling", + "polling 2.8.0", "socket2", ] @@ -2635,7 +2872,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 +2940,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 +3189,7 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.6.0", "block2", + "dispatch", "libc", "objc2", ] @@ -3081,6 +3331,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 +3584,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 +3630,7 @@ checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", "indexmap 2.6.0", - "quick-xml", + "quick-xml 0.32.0", "serde", "time", ] @@ -3393,6 +3664,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 +3798,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 +4019,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 +4407,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 +4997,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 +5322,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 +5404,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 +5665,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.52.0", ] @@ -5484,6 +5870,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 +6204,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 +6832,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 +6866,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 +7008,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", +] diff --git a/crates/lanspread-client/src/lib.rs b/crates/lanspread-client/src/lib.rs index e6650f5..1432bc0 100644 --- a/crates/lanspread-client/src/lib.rs +++ b/crates/lanspread-client/src/lib.rs @@ -1,10 +1,12 @@ +#![allow(clippy::missing_errors_doc)] + use std::{net::SocketAddr, time::Duration}; use bytes::{Bytes, BytesMut}; use lanspread_db::db::Game; use lanspread_proto::{Message as _, Request, Response}; use lanspread_utils::maybe_addr; -use s2n_quic::{client::Connect, provider::limits::Limits, Client as QuicClient}; +use s2n_quic::{client::Connect, provider::limits::Limits, Client as QuicClient, Connection}; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; static CERT_PEM: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../cert.pem")); @@ -21,7 +23,39 @@ pub enum ClientCommand { ServerAddr(SocketAddr), } -/// # 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 +} + +#[allow(clippy::too_many_lines)] pub async fn run( mut rx_control: UnboundedReceiver, tx_notify_ui: UnboundedSender, @@ -58,6 +92,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()), @@ -131,6 +169,7 @@ pub async fn run( String::from_utf8_lossy(&data) ); } + Response::Pong => (), // ignore (should never happen) } if let Err(err) = tx.close().await { diff --git a/crates/lanspread-db/src/db.rs b/crates/lanspread-db/src/db.rs index 6de0f7d..0ea0b94 100644 --- a/crates/lanspread-db/src/db.rs +++ b/crates/lanspread-db/src/db.rs @@ -29,6 +29,8 @@ pub struct Game { pub size: u64, /// thumbnail image pub thumbnail: Option, + /// 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) -> 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(&mut self, id: S) -> Option<&mut Game> + where + S: AsRef, + { + 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,16 @@ 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() } } diff --git a/crates/lanspread-proto/src/lib.rs b/crates/lanspread-proto/src/lib.rs index b613b87..f99aaaa 100644 --- a/crates/lanspread-proto/src/lib.rs +++ b/crates/lanspread-proto/src/lib.rs @@ -5,6 +5,7 @@ use tracing::error; #[derive(Debug, Serialize, Deserialize)] pub enum Request { + Ping, ListGames, GetGame { id: String }, Invalid(Bytes, String), @@ -12,6 +13,7 @@ pub enum Request { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Response { + Pong, Games(Vec), Game(Game), GameNotFound(String), diff --git a/crates/lanspread-server/src/main.rs b/crates/lanspread-server/src/main.rs index 862d328..4bb1525 100644 --- a/crates/lanspread-server/src/main.rs +++ b/crates/lanspread-server/src/main.rs @@ -116,6 +116,7 @@ impl RequestHandler { async fn handle_request(&self, request: Request) -> Response { match request { + Request::Ping => Response::Pong, Request::ListGames => { let db = self.db.lock().await; Response::Games(db.all_games().into_iter().cloned().collect()) @@ -172,6 +173,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, } } diff --git a/crates/lanspread-tauri-deno-ts/deno.lock b/crates/lanspread-tauri-deno-ts/deno.lock index 598a178..21a0474 100644 --- a/crates/lanspread-tauri-deno-ts/deno.lock +++ b/crates/lanspread-tauri-deno-ts/deno.lock @@ -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", diff --git a/crates/lanspread-tauri-deno-ts/package.json b/crates/lanspread-tauri-deno-ts/package.json index 019e20a..36e00dd 100644 --- a/crates/lanspread-tauri-deno-ts/package.json +++ b/crates/lanspread-tauri-deno-ts/package.json @@ -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", diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml b/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml index 7ca01bd..cf63eb8 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml +++ b/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml @@ -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" diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/capabilities/default.json b/crates/lanspread-tauri-deno-ts/src-tauri/capabilities/default.json index 3bb4cc4..dbd54dc 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/capabilities/default.json +++ b/crates/lanspread-tauri-deno-ts/src-tauri/capabilities/default.json @@ -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" ] -} +} \ No newline at end of file diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs index 1bc2d53..9ae3eea 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs +++ b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs @@ -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>, client_ctrl: UnboundedSender, + games: Arc>, } #[tauri::command] @@ -40,6 +46,67 @@ fn run_game_backend(id: String, state: tauri::State) -> 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) { + 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::() + .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 +124,7 @@ async fn find_server(app: AppHandle) { .client_ctrl .send(ClientCommand::ServerAddr(server_addr)) .unwrap(); + request_games(state); break; } Err(e) => { @@ -66,6 +134,25 @@ async fn find_server(app: AppHandle) { } } +async fn update_game_db(games: Vec, app: AppHandle) { + for game in &games { + log::trace!("client event ListGames iter: {game:?}"); + } + + let state = app.state::(); + + // 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 +176,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![ + run_game_backend, + request_games, + update_game_directory + ]) .manage(lanspread_state) .setup(|app| { let app_handle = app.handle().clone(); @@ -110,16 +204,7 @@ 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:?}"); - } - - 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"); - } + update_game_db(games, app_handle.clone()).await; } } } diff --git a/crates/lanspread-tauri-deno-ts/src/App.css b/crates/lanspread-tauri-deno-ts/src/App.css index de45941..b76b6ee 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.css +++ b/crates/lanspread-tauri-deno-ts/src/App.css @@ -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; +} diff --git a/crates/lanspread-tauri-deno-ts/src/App.tsx b/crates/lanspread-tauri-deno-ts/src/App.tsx index e507a5b..7d3df2b 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.tsx +++ b/crates/lanspread-tauri-deno-ts/src/App.tsx @@ -1,40 +1,78 @@ 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([]); 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(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 +82,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,7 +94,6 @@ const App = () => { // Cleanup return () => { console.log('๐Ÿšซ Effect cleanup - component unmounting'); - isSubscribed = false; }; }, []); // Empty dependency array means this runs once on mount @@ -71,20 +107,38 @@ const App = () => { } }; + const dialogGameDir = async () => { + const file = await open({ + multiple: false, + directory: true, + }); + + if (file) { + setGameDir(file); + } + }; + // Rest of your component remains the same return (

SoftLAN Launcher

-
- setSearchTerm(e.target.value)} - className="search-input" - /> +
+
+
+ setSearchTerm(e.target.value)} + className="search-input" + /> +
+
+ + {gameDir} +
@@ -101,7 +155,7 @@ const App = () => { {item.description.slice(0, 10)} {item.size.toString()}
-
Play
+
{item.installed ? 'Play' : 'Install'}
); })}