From b4585b663ad27001dd879ae27e0c578b660ec57a Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 2 May 2026 15:31:37 +0200 Subject: [PATCH] ChatGPT Codex 5.5 xhigh refactored even more --- Cargo.lock | 1366 ++++++++--------- Cargo.toml | 5 - crates/lanspread-compat/Cargo.toml | 4 + crates/lanspread-db/Cargo.toml | 6 +- crates/lanspread-mdns/Cargo.toml | 6 +- crates/lanspread-peer/Cargo.toml | 6 +- crates/lanspread-peer/src/events.rs | 55 + crates/lanspread-peer/src/handlers.rs | 55 +- crates/lanspread-peer/src/lib.rs | 2 + crates/lanspread-peer/src/peer_db.rs | 4 +- crates/lanspread-peer/src/remote_peer.rs | 59 + crates/lanspread-peer/src/services.rs | 1243 +-------------- .../lanspread-peer/src/services/advertise.rs | 91 ++ .../lanspread-peer/src/services/discovery.rs | 170 ++ .../lanspread-peer/src/services/handshake.rs | 267 ++++ crates/lanspread-peer/src/services/legacy.rs | 41 + .../lanspread-peer/src/services/liveness.rs | 223 +++ .../src/services/local_monitor.rs | 38 + crates/lanspread-peer/src/services/server.rs | 79 + crates/lanspread-peer/src/services/stream.rs | 397 +++++ crates/lanspread-proto/Cargo.toml | 4 + .../src-tauri/Cargo.toml | 5 +- .../src-tauri/src/lib.rs | 2 +- crates/lanspread-utils/Cargo.toml | 4 +- 24 files changed, 2160 insertions(+), 1972 deletions(-) create mode 100644 crates/lanspread-peer/src/events.rs create mode 100644 crates/lanspread-peer/src/remote_peer.rs create mode 100644 crates/lanspread-peer/src/services/advertise.rs create mode 100644 crates/lanspread-peer/src/services/discovery.rs create mode 100644 crates/lanspread-peer/src/services/handshake.rs create mode 100644 crates/lanspread-peer/src/services/legacy.rs create mode 100644 crates/lanspread-peer/src/services/liveness.rs create mode 100644 crates/lanspread-peer/src/services/local_monitor.rs create mode 100644 crates/lanspread-peer/src/services/server.rs create mode 100644 crates/lanspread-peer/src/services/stream.rs diff --git a/Cargo.lock b/Cargo.lock index a0f2735..573bbc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arrayvec" @@ -133,9 +133,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -166,6 +166,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -174,9 +189,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -213,25 +228,26 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -257,9 +273,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-unit" @@ -322,7 +338,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -385,9 +401,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -435,10 +451,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "chrono" -version = "0.4.43" +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", @@ -448,9 +475,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -474,12 +501,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "cookie" version = "0.18.1" @@ -508,11 +529,11 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-graphics-types", "foreign-types", @@ -525,7 +546,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "libc", ] @@ -539,6 +560,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -550,9 +580,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" @@ -599,19 +629,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.29.6" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", + "phf 0.13.1", "smallvec", - "syn 1.0.109", ] [[package]] @@ -621,19 +647,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "ctor" -version = "0.2.9" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" dependencies = [ - "quote", - "syn 2.0.116", + "ctor-proc-macro", + "dtor", ] +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "cuckoofilter" version = "0.5.0" @@ -647,9 +679,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -657,34 +689,44 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.116", + "syn 2.0.117", +] + +[[package]] +name = "dbus" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", ] [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -692,15 +734,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.20" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -734,19 +784,13 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -760,7 +804,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -783,7 +827,22 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash 0.2.0", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", ] [[package]] @@ -816,6 +875,21 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dunce" version = "1.0.5" @@ -839,14 +913,14 @@ dependencies = [ [[package]] name = "embed-resource" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "vswhom", "winreg", ] @@ -884,9 +958,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", @@ -926,9 +1000,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" @@ -1021,7 +1095,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1051,16 +1125,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" version = "0.3.32" @@ -1128,7 +1192,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1160,15 +1224,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "gdk" version = "0.18.2" @@ -1318,19 +1373,20 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1373,7 +1429,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -1401,7 +1457,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1480,7 +1536,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1511,9 +1567,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" dependencies = [ "allocator-api2", "equivalent", @@ -1555,14 +1611,12 @@ checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "html5ever" -version = "0.29.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" dependencies = [ "log", - "mac", "markup5ever", - "match_token", ] [[package]] @@ -1606,9 +1660,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1619,7 +1673,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1679,17 +1732,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ "byteorder", - "png", + "png 0.17.16", ] [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1697,9 +1751,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1710,9 +1764,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1724,15 +1778,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1744,15 +1798,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1788,9 +1842,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1825,12 +1879,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1855,15 +1909,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -1890,9 +1944,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "javascriptcore-rs" @@ -1926,7 +1980,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -1935,9 +1989,31 @@ dependencies = [ [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] name = "jobserver" @@ -1951,10 +2027,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1987,23 +2065,11 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "serde", "unicode-segmentation", ] -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.13.0", - "selectors", -] - [[package]] name = "lanspread-compat" version = "0.1.0" @@ -2019,9 +2085,7 @@ dependencies = [ name = "lanspread-db" version = "0.1.0" dependencies = [ - "bytes", "eyre", - "semver", "serde", "serde_json", "tracing", @@ -2034,8 +2098,6 @@ dependencies = [ "eyre", "log", "mdns-sd", - "tokio", - "tracing", ] [[package]] @@ -2047,7 +2109,6 @@ dependencies = [ "futures", "gethostname", "if-addrs", - "lanspread-compat", "lanspread-db", "lanspread-mdns", "lanspread-proto", @@ -2058,7 +2119,6 @@ dependencies = [ "serde_json", "tokio", "tokio-util", - "tracing", "uuid", "walkdir", ] @@ -2082,12 +2142,9 @@ dependencies = [ "eyre", "lanspread-compat", "lanspread-db", - "lanspread-mdns", "lanspread-peer", "log", "mimalloc", - "serde", - "serde_json", "tauri", "tauri-build", "tauri-plugin-dialog", @@ -2141,9 +2198,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] [[package]] name = "libloading" @@ -2163,21 +2229,19 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.44" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" dependencies = [ "cc", - "libc", ] [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", "libc", ] @@ -2194,15 +2258,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2222,48 +2286,22 @@ dependencies = [ "value-bag", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "markup5ever" -version = "0.14.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" dependencies = [ "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", "tendril", + "web_atoms", ] -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.116", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "mdns-sd" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c23fd301a08742925d79fd29bba538c9cb0408d67907905f8f9d4e5cd8bc816" +checksum = "d797ab3274a16f4940f9650a29838e940223aeff31773df5c2827ad82150182f" dependencies = [ "fastrand", "flume", @@ -2291,9 +2329,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.48" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" dependencies = [ "libmimalloc-sys", ] @@ -2316,9 +2354,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -2328,9 +2366,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.17.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb" dependencies = [ "crossbeam-channel", "dpi", @@ -2341,10 +2379,10 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png", + "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2353,8 +2391,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", - "jni-sys", + "bitflags 2.11.1", + "jni-sys 0.3.1", "log", "ndk-sys", "num_enum", @@ -2362,19 +2400,13 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - [[package]] name = "ndk-sys" version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.1", ] [[package]] @@ -2383,17 +2415,11 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2426,9 +2452,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -2436,14 +2462,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2457,9 +2483,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", "objc2-exception-helper", @@ -2471,19 +2497,11 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", - "libc", "objc2", - "objc2-cloud-kit", - "objc2-core-data", "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", "objc2-foundation", - "objc2-quartz-core", ] [[package]] @@ -2492,7 +2510,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-foundation", ] @@ -2503,7 +2521,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "bitflags 2.11.0", "objc2", "objc2-foundation", ] @@ -2514,7 +2531,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2", ] @@ -2525,7 +2542,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2", "objc2-core-foundation", @@ -2542,31 +2559,28 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-text" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-core-graphics", ] -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - [[package]] name = "objc2-encode" version = "4.1.0" @@ -2588,7 +2602,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -2601,17 +2615,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" -dependencies = [ + "bitflags 2.11.1", "objc2", "objc2-core-foundation", ] @@ -2622,32 +2626,40 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-foundation", ] -[[package]] -name = "objc2-security" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", -] - [[package]] name = "objc2-ui-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", + "block2", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", "objc2-foundation", ] @@ -2657,27 +2669,25 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-app-kit", "objc2-core-foundation", "objc2-foundation", - "objc2-javascript-core", - "objc2-security", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "open" -version = "5.3.3" +version = "5.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" dependencies = [ "dunce", "is-wsl", @@ -2767,26 +2777,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - [[package]] name = "phf" version = "0.11.3" @@ -2798,43 +2788,24 @@ dependencies = [ ] [[package]] -name = "phf_codegen" -version = "0.8.0" +name = "phf" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", ] [[package]] name = "phf_codegen" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", + "phf_generator 0.13.1", + "phf_shared 0.13.1", ] [[package]] @@ -2844,21 +2815,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] -name = "phf_macros" -version = "0.10.0" +name = "phf_generator" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", + "fastrand", + "phf_shared 0.13.1", ] [[package]] @@ -2871,25 +2838,20 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] -name = "phf_shared" -version = "0.8.0" +name = "phf_macros" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2898,35 +2860,38 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.2", + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ "base64 0.22.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "quick-xml", "serde", "time", @@ -2946,10 +2911,23 @@ dependencies = [ ] [[package]] -name = "potential_utf" -version = "0.1.4" +name = "png" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2982,7 +2960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3007,11 +2985,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -3038,12 +3016,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -3075,18 +3047,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3097,6 +3069,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -3114,14 +3092,13 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", - "rand_pcg", ] [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3130,12 +3107,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -3158,16 +3136,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -3188,12 +3156,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_hc" @@ -3204,15 +3169,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3225,7 +3181,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3256,7 +3212,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3284,9 +3240,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rend" @@ -3299,9 +3255,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -3400,20 +3356,27 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "rkyv", "serde", "serde_json", + "wasm-bindgen", ] +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -3425,11 +3388,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -3438,9 +3401,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -3453,18 +3416,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -3486,9 +3449,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "s2n-codec" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a07f4fac48aa62fe8e70ee05eb8671d143689cbb950ad6cca2e7e52313a093" +checksum = "22bacdd8e47b929c10802a93070890d26b8c0e510857113b8f098fca002aecf4" dependencies = [ "byteorder", "bytes", @@ -3497,17 +3460,16 @@ dependencies = [ [[package]] name = "s2n-quic" -version = "1.74.0" +version = "1.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab472129b56875c4e42e055903ce95523291a142223b331d1544e3dd9a702293" +checksum = "82c23e80ef5205e7f197f6fb0922f3f66eb6599fd826cbf5eb0dc0edba17aee5" dependencies = [ "bytes", "cfg-if", "cuckoofilter", "futures", "hash_hasher", - "rand 0.9.2", - "rand_chacha 0.9.0", + "rand 0.10.1", "s2n-codec", "s2n-quic-core", "s2n-quic-crypto", @@ -3522,9 +3484,9 @@ dependencies = [ [[package]] name = "s2n-quic-core" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f526ee912db4354dffc6229d283ac9d7cf297ec1bee8e495b786a7e520138b2" +checksum = "8b0d2cd0e789568ac5c374ea7fbf599e1a9d63362bd37baf2d45d386f5f3d25f" dependencies = [ "atomic-waker", "byteorder", @@ -3544,9 +3506,9 @@ dependencies = [ [[package]] name = "s2n-quic-crypto" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aec6bbed663012d824ee279833959800837453a32d118d351d971da2ffc0f363" +checksum = "d0b6fdfaca6213006dc5163ad8788010f6b5861e6e41352b0a3b00e0fc0930d4" dependencies = [ "aws-lc-rs", "cfg-if", @@ -3558,9 +3520,9 @@ dependencies = [ [[package]] name = "s2n-quic-platform" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7229151d4a48d17cec48d274c0c74c82220ddef932aae91fdc18d9beab7097bc" +checksum = "7c4742176bbbc522d78e415dfdd3fba84b09ce4744bce20b2f7a6e8ff0c62a3a" dependencies = [ "cfg-if", "futures", @@ -3573,9 +3535,9 @@ dependencies = [ [[package]] name = "s2n-quic-rustls" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fff17e3c7bbde694b1cadacfb42aae93d43d57dbc77b726bdbd67518e51b2dd" +checksum = "201a367dbdadc85c9e1fda1be03205740bf2cc848f9436189f88d9b7f3807554" dependencies = [ "bytes", "rustls", @@ -3587,9 +3549,9 @@ dependencies = [ [[package]] name = "s2n-quic-tls" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c35e1159a13fd9602905ea0e0c4c0a7d023d62680b8ea1ff73f9c1de1070f6" +checksum = "5368d27604ea26aab1dac1df1a84b676a519e139d7927d9afbd6a8137c9129b7" dependencies = [ "bytes", "errno", @@ -3602,9 +3564,9 @@ dependencies = [ [[package]] name = "s2n-quic-tls-default" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c14d35fee2f8997241176e7a191d11cd928e0ff6638bd6aafddf4f3d8d24ff6c" +checksum = "e710c8e03558c1eaca90356f34821f6d3b01bf4bf9cc439935cb3002eab2e1d4" dependencies = [ "s2n-quic-rustls", "s2n-quic-tls", @@ -3612,27 +3574,27 @@ dependencies = [ [[package]] name = "s2n-quic-transport" -version = "0.74.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5627b2561dba83b25908d49afa87ff27afd6f04a4cb3679731349db71967dc16" +checksum = "08db5b4830452c1fff9c9fa93a3fa030c1d7d38a9fc96815e0df98e407fae612" dependencies = [ "bytes", "futures-channel", "futures-core", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "intrusive-collections", "once_cell", "s2n-codec", "s2n-quic-core", - "siphasher 1.0.2", + "siphasher", "smallvec", ] [[package]] name = "s2n-tls" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ed0b0786afe33ad792aadf289629d39cb45edc7ab1250a6b6cdf71ebc1c026" +checksum = "5d22c36f207d0bb6a38272d5d1fa1cd99094335c70db1bfef54e0b8b672ae26f" dependencies = [ "errno", "hex", @@ -3643,9 +3605,9 @@ dependencies = [ [[package]] name = "s2n-tls-sys" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1333e7ea5fbc8f36ef4c3c4aa0269441195cee56bef783f0d418151eebb9eac" +checksum = "bcf9e3b136cdd2c99f0cee2811d80b9f6bc44b5fd48c172857de32babb506f5e" dependencies = [ "aws-lc-rs", "cc", @@ -3709,7 +3671,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3726,27 +3688,28 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "selectors" -version = "0.24.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "cssparser", "derive_more", - "fxhash", "log", - "phf 0.8.0", - "phf_codegen 0.8.0", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen", "precomputed-hash", + "rustc-hash", "servo_arc", "smallvec", ] [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -3791,7 +3754,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3802,7 +3765,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3826,7 +3789,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3840,9 +3803,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -3861,15 +3824,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -3880,14 +3843,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3909,16 +3872,15 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "servo_arc" -version = "0.2.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" dependencies = [ - "nodrop", "stable_deref_trait", ] @@ -3929,7 +3891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3983,9 +3945,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simdutf8" @@ -3993,12 +3955,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.2" @@ -4030,12 +3986,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4124,7 +4080,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "memchr", "once_cell", @@ -4149,7 +4105,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4170,7 +4126,7 @@ dependencies = [ "sha2", "sqlx-core", "sqlx-sqlite", - "syn 2.0.116", + "syn 2.0.117", "tokio", "url", ] @@ -4207,25 +4163,24 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "string_cache" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared 0.11.3", + "phf_shared 0.13.1", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", ] @@ -4266,9 +4221,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4292,7 +4247,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4310,35 +4265,35 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.5" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +checksum = "1cf65722394c2ac443e80120064987f8914ee1d4e4e36e63cdf10f2990f01159" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "core-foundation", "core-graphics", "crossbeam-channel", - "dispatch", + "dbus", + "dispatch2", "dlopen2", "dpi", "gdkwayland-sys", "gdkx11-sys", "gtk", "jni", - "lazy_static", "libc", "log", "ndk", - "ndk-context", "ndk-sys", "objc2", "objc2-app-kit", "objc2-foundation", + "objc2-ui-kit", "once_cell", "parking_lot", + "percent-encoding", "raw-window-handle", - "scopeguard", "tao-macros", "unicode-segmentation", "url", @@ -4356,7 +4311,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4373,9 +4328,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.10.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" +checksum = "d059f2527558d9dba6f186dec4772610e1aecfd3f94002397613e7e648752b66" dependencies = [ "anyhow", "bytes", @@ -4424,9 +4379,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.5.5" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" +checksum = "be9aa8c59a894f76c29a002501c589de5eb4987a5913d62a6e0a47f320901988" dependencies = [ "anyhow", "cargo_toml", @@ -4440,29 +4395,28 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.5.4" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" +checksum = "d3e4e8230d565106aa19dfbaa01a7ed01abf78047fe0577a83377224bd1bf20e" dependencies = [ "base64 0.22.1", "brotli", "ico", "json-patch", "plist", - "png", + "png 0.17.16", "proc-macro2", "quote", "semver", "serde", "serde_json", "sha2", - "syn 2.0.116", + "syn 2.0.117", "tauri-utils", "thiserror 2.0.18", "time", @@ -4473,23 +4427,23 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.5.4" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" +checksum = "bc8de2cddbbc33dbdf4c84f170121886595efdbcc9cb4b3d76342b79d082cedc" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-plugin" -version = "2.5.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" +checksum = "f8d5f58bfd0cdcfdbc0a68dc08b354eea2afc551b421de91b07b69e0dd769d57" dependencies = [ "anyhow", "glob", @@ -4498,15 +4452,14 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-plugin-dialog" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" +checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" dependencies = [ "log", "raw-window-handle", @@ -4522,13 +4475,15 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.4.5" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" dependencies = [ "anyhow", "dunce", "glob", + "log", + "objc2-foundation", "percent-encoding", "schemars 0.8.22", "serde", @@ -4538,7 +4493,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "url", ] @@ -4587,9 +4542,9 @@ dependencies = [ [[package]] name = "tauri-plugin-store" -version = "2.4.2" +version = "2.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca1a8ff83c269b115e98726ffc13f9e548a10161544a92ad121d6d0a96e16ea" +checksum = "6c72dda16786eb4a3f903e43a17b64d8d78dc0f00fe2aa4b757c28f617a8630b" dependencies = [ "dunce", "serde", @@ -4603,9 +4558,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" +checksum = "1e42bbcb76237351fbaa02f08d808c537dc12eb5a6eabbf3e517b50056334d95" dependencies = [ "cookie", "dpi", @@ -4628,9 +4583,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" +checksum = "2cadb13dad0c681e1e0a2c49ae488f0e2906ded3d57e7a0017f4aaf46e387117" dependencies = [ "gtk", "http", @@ -4638,7 +4593,6 @@ dependencies = [ "log", "objc2", "objc2-app-kit", - "objc2-foundation", "once_cell", "percent-encoding", "raw-window-handle", @@ -4655,24 +4609,24 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.8.2" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" +checksum = "55f61d2bf7188fbcf2b0ed095b67a6bc498f713c939314bb19eb700118a573b7" dependencies = [ "anyhow", "brotli", "cargo_metadata", "ctor", + "dom_query", "dunce", "glob", - "html5ever", "http", "infer", "json-patch", - "kuchikiki", "log", "memchr", "phf 0.11.3", + "plist", "proc-macro2", "quote", "regex", @@ -4684,7 +4638,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "url", "urlpattern", "uuid", @@ -4693,23 +4647,22 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ "dunce", "embed-resource", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", ] [[package]] name = "tendril" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" dependencies = [ - "futf", - "mac", + "new_debug_unreachable", "utf-8", ] @@ -4739,7 +4692,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4750,7 +4703,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4788,9 +4741,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -4798,9 +4751,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4813,9 +4766,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -4830,13 +4783,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4881,13 +4834,28 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_core", - "serde_spanned 1.0.4", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.14", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.2", ] [[package]] @@ -4908,13 +4876,22 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -4925,7 +4902,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -4934,30 +4911,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.7.5+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow 1.0.2", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 0.7.14", + "winnow 1.0.2", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -4980,7 +4957,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", @@ -5024,7 +5001,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5038,9 +5015,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.21.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" dependencies = [ "crossbeam-channel", "dirs", @@ -5052,10 +5029,10 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png", + "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5072,9 +5049,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unic-char-property" @@ -5125,9 +5102,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -5192,11 +5169,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.21.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -5279,11 +5256,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -5292,14 +5269,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -5310,23 +5287,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5334,22 +5307,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -5371,7 +5344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -5395,22 +5368,34 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "web_atoms" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" +dependencies = [ + "phf 0.13.1", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + [[package]] name = "webkit2gtk" version = "2.0.2" @@ -5477,7 +5462,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5636,7 +5621,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5647,7 +5632,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5987,9 +5972,15 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -6013,6 +6004,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -6032,9 +6029,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -6050,7 +6047,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -6062,8 +6059,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -6082,7 +6079,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "semver", "serde", @@ -6094,30 +6091,29 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" -version = "0.54.2" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" +checksum = "3013fd6116aac351dd2e18f349b28b2cfef3a5ff3253a9d0ce2d7193bb1b4429" dependencies = [ "base64 0.22.1", "block2", "cookie", "crossbeam-channel", "dirs", + "dom_query", "dpi", "dunce", "gdkx11", "gtk", - "html5ever", "http", "javascriptcore-rs", "jni", - "kuchikiki", "libc", "ndk", "objc2", @@ -6175,9 +6171,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -6186,54 +6182,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -6254,14 +6250,14 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -6270,9 +6266,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -6281,13 +6277,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e74ed81..2bc19f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,18 +13,14 @@ members = [ [workspace.dependencies] base64 = "0.22" bytes = { version = "1", features = ["serde"] } -chrono = "0.4" -clap = { version = "4", features = ["derive"] } eyre = "0.6" futures = "0.3" gethostname = "1" if-addrs = "0.15" -itertools = "0.14" log = "0.4" mdns-sd = "0.18" mimalloc = { version = "0.1", features = ["secure"] } s2n-quic = { version = "1", features = ["provider-event-tracing"] } -semver = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" sqlx = { version = "0.8", default-features = false, features = [ @@ -40,7 +36,6 @@ tauri-plugin-store = "2" tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7", features = ["codec"] } tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } uuid = { version = "1", features = ["v7"] } walkdir = "2" windows = { version = "0.62", features = [ diff --git a/crates/lanspread-compat/Cargo.toml b/crates/lanspread-compat/Cargo.toml index 9f4a858..2418d6b 100644 --- a/crates/lanspread-compat/Cargo.toml +++ b/crates/lanspread-compat/Cargo.toml @@ -19,3 +19,7 @@ eyre = { workspace = true } sqlx = { workspace = true } serde = { workspace = true } tracing = { workspace = true } + +[lib] +test = false +doctest = false diff --git a/crates/lanspread-db/Cargo.toml b/crates/lanspread-db/Cargo.toml index 58b3b50..dbb3e9d 100644 --- a/crates/lanspread-db/Cargo.toml +++ b/crates/lanspread-db/Cargo.toml @@ -12,10 +12,10 @@ todo = "warn" unwrap_used = "warn" [dependencies] -# external -bytes = { workspace = true } eyre = { workspace = true } -semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tracing = { workspace = true } + +[lib] +doctest = false diff --git a/crates/lanspread-mdns/Cargo.toml b/crates/lanspread-mdns/Cargo.toml index 6ded37b..f3a2d29 100644 --- a/crates/lanspread-mdns/Cargo.toml +++ b/crates/lanspread-mdns/Cargo.toml @@ -15,5 +15,7 @@ unwrap_used = "warn" eyre = { workspace = true } log = { workspace = true } mdns-sd = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } + +[lib] +test = false +doctest = false diff --git a/crates/lanspread-peer/Cargo.toml b/crates/lanspread-peer/Cargo.toml index fbdd779..bb87534 100644 --- a/crates/lanspread-peer/Cargo.toml +++ b/crates/lanspread-peer/Cargo.toml @@ -9,8 +9,6 @@ todo = "warn" unwrap_used = "warn" [dependencies] -# local -lanspread-compat = { path = "../lanspread-compat" } lanspread-db = { path = "../lanspread-db" } lanspread-mdns = { path = "../lanspread-mdns" } lanspread-proto = { path = "../lanspread-proto" } @@ -28,6 +26,8 @@ serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } -tracing = { workspace = true } uuid = { workspace = true } walkdir = { workspace = true } + +[lib] +doctest = false diff --git a/crates/lanspread-peer/src/events.rs b/crates/lanspread-peer/src/events.rs new file mode 100644 index 0000000..e637272 --- /dev/null +++ b/crates/lanspread-peer/src/events.rs @@ -0,0 +1,55 @@ +//! UI event helpers used by peer command and service code. + +use std::{net::SocketAddr, sync::Arc}; + +use tokio::sync::{RwLock, mpsc::UnboundedSender}; + +use crate::{PeerEvent, peer_db::PeerGameDB}; + +pub fn send(tx_notify_ui: &UnboundedSender, event: PeerEvent, label: &str) { + if let Err(err) = tx_notify_ui.send(event) { + log::error!("Failed to send {label} event: {err}"); + } +} + +pub async fn emit_peer_game_list( + peer_game_db: &Arc>, + tx_notify_ui: &UnboundedSender, +) { + let games = { peer_game_db.read().await.get_all_games() }; + send(tx_notify_ui, PeerEvent::ListGames(games), "ListGames"); +} + +pub async fn emit_peer_count( + peer_game_db: &Arc>, + tx_notify_ui: &UnboundedSender, +) { + let peer_count = { peer_game_db.read().await.get_peer_addresses().len() }; + send( + tx_notify_ui, + PeerEvent::PeerCountUpdated(peer_count), + "PeerCountUpdated", + ); +} + +pub async fn emit_peer_discovered( + peer_game_db: &Arc>, + tx_notify_ui: &UnboundedSender, + peer_addr: SocketAddr, +) { + send( + tx_notify_ui, + PeerEvent::PeerDiscovered(peer_addr), + "PeerDiscovered", + ); + emit_peer_count(peer_game_db, tx_notify_ui).await; +} + +pub async fn emit_peer_lost( + peer_game_db: &Arc>, + tx_notify_ui: &UnboundedSender, + peer_addr: SocketAddr, +) { + send(tx_notify_ui, PeerEvent::PeerLost(peer_addr), "PeerLost"); + emit_peer_count(peer_game_db, tx_notify_ui).await; +} diff --git a/crates/lanspread-peer/src/handlers.rs b/crates/lanspread-peer/src/handlers.rs index 9b58740..aa68c28 100644 --- a/crates/lanspread-peer/src/handlers.rs +++ b/crates/lanspread-peer/src/handlers.rs @@ -2,19 +2,24 @@ use std::{net::SocketAddr, sync::Arc}; -use lanspread_db::db::{Game, GameFileDescription}; +use lanspread_db::db::GameFileDescription; use tokio::sync::{RwLock, mpsc::UnboundedSender}; use crate::{ PeerEvent, context::Ctx, download::download_game_files, + events, identity::FEATURE_LIBRARY_DELTA, local_games::{ - LocalLibraryScan, get_game_file_descriptions, local_download_available, scan_local_library, + LocalLibraryScan, + get_game_file_descriptions, + local_download_available, + scan_local_library, }, network::{announce_games_to_peer, request_game_details_from_peer, send_library_delta}, - peer_db::{PeerGameDB, PeerId}, + peer_db::PeerGameDB, + remote_peer::ensure_peer_id_for_addr, }; // ============================================================================= @@ -24,32 +29,7 @@ use crate::{ /// Handles the `ListGames` command. pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender) { log::info!("ListGames command received"); - emit_peer_game_list(&ctx.peer_game_db, tx_notify_ui).await; -} - -/// Emits the aggregated game list to the UI. -pub async fn emit_peer_game_list( - peer_game_db: &Arc>, - tx_notify_ui: &UnboundedSender, -) { - let all_games = { peer_game_db.read().await.get_all_games() }; - if let Err(e) = tx_notify_ui.send(PeerEvent::ListGames(all_games)) { - log::error!("Failed to send ListGames event: {e}"); - } -} - -async fn ensure_peer_id_for_addr( - peer_game_db: &Arc>, - peer_addr: SocketAddr, -) -> PeerId { - let mut db = peer_game_db.write().await; - if let Some(peer_id) = db.peer_id_for_addr(&peer_addr).cloned() { - return peer_id; - } - - let legacy_id = format!("legacy-{peer_addr}"); - db.upsert_peer(legacy_id.clone(), peer_addr); - legacy_id + events::emit_peer_game_list(&ctx.peer_game_db, tx_notify_ui).await; } /// Tries to serve a game from local files. @@ -165,12 +145,10 @@ pub async fn handle_download_game_files_command( ) { log::info!("Got PeerCommand::DownloadGameFiles"); let games_folder = { ctx.game_dir.read().await.clone() }; - if games_folder.is_none() { + let Some(games_folder) = games_folder else { log::error!("Cannot handle game file descriptions: games_folder is not set"); return; - } - - let games_folder = games_folder.expect("checked above"); + }; // Use majority validation to get trusted file descriptions and peer whitelist let (validated_descriptions, peer_whitelist, file_peer_map) = { @@ -312,10 +290,7 @@ pub async fn handle_set_game_dir_command( /// Handles the `GetPeerCount` command. pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender) { log::info!("GetPeerCount command received"); - let peer_count = { ctx.peer_game_db.read().await.get_peer_addresses().len() }; - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerCountUpdated(peer_count)) { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } + events::emit_peer_count(&ctx.peer_game_db, tx_notify_ui).await; } // ============================================================================= @@ -348,11 +323,7 @@ pub async fn update_and_announce_games( *db_guard = Some(game_db.clone()); } - let all_games = game_db - .all_games() - .into_iter() - .cloned() - .collect::>(); + let all_games = game_db.all_games().into_iter().cloned().collect::>(); if let Err(e) = tx_notify_ui.send(PeerEvent::LocalGamesUpdated(all_games.clone())) { log::error!("Failed to send LocalGamesUpdated event: {e}"); diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index c162885..c271a15 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -16,6 +16,7 @@ mod config; mod context; mod download; mod error; +mod events; mod handlers; mod identity; mod library; @@ -24,6 +25,7 @@ mod network; mod path_validation; mod peer; mod peer_db; +mod remote_peer; mod services; // ============================================================================= diff --git a/crates/lanspread-peer/src/peer_db.rs b/crates/lanspread-peer/src/peer_db.rs index 1d52009..850666a 100644 --- a/crates/lanspread-peer/src/peer_db.rs +++ b/crates/lanspread-peer/src/peer_db.rs @@ -409,8 +409,8 @@ impl PeerGameDB { #[must_use] pub fn game_files_for(&self, game_id: &str) -> Vec<(SocketAddr, Vec)> { self.peers - .iter() - .filter_map(|(_, peer)| { + .values() + .filter_map(|peer| { peer.files .get(game_id) .cloned() diff --git a/crates/lanspread-peer/src/remote_peer.rs b/crates/lanspread-peer/src/remote_peer.rs new file mode 100644 index 0000000..d49a040 --- /dev/null +++ b/crates/lanspread-peer/src/remote_peer.rs @@ -0,0 +1,59 @@ +//! Shared helpers for remote peer identity and legacy game announcements. + +use std::{collections::HashMap, net::SocketAddr, sync::Arc}; + +use lanspread_db::db::Game; +use lanspread_proto::{Availability, GameSummary}; +use tokio::sync::RwLock; + +use crate::{ + library::compute_library_digest, + peer_db::{PeerGameDB, PeerId}, +}; + +pub async fn ensure_peer_id_for_addr( + peer_game_db: &Arc>, + peer_addr: SocketAddr, +) -> PeerId { + let mut db = peer_game_db.write().await; + if let Some(peer_id) = db.peer_id_for_addr(&peer_addr).cloned() { + return peer_id; + } + + let legacy_id = format!("legacy-{peer_addr}"); + db.upsert_peer(legacy_id.clone(), peer_addr); + legacy_id +} + +pub fn summary_from_game(game: &Game) -> GameSummary { + GameSummary { + id: game.id.clone(), + name: game.name.clone(), + size: game.size, + downloaded: game.downloaded, + installed: game.installed, + eti_version: game.eti_game_version.clone(), + manifest_hash: 0, + availability: Availability::Ready, + } +} + +pub async fn update_peer_from_game_list( + peer_game_db: &Arc>, + peer_addr: SocketAddr, + games: &[Game], +) -> Vec { + let summaries = games.iter().map(summary_from_game).collect::>(); + let mut by_id = HashMap::with_capacity(summaries.len()); + for summary in &summaries { + by_id.insert(summary.id.clone(), summary.clone()); + } + let digest = compute_library_digest(&by_id); + let peer_id = ensure_peer_id_for_addr(peer_game_db, peer_addr).await; + + let mut db = peer_game_db.write().await; + db.update_peer_games(&peer_id, summaries); + let features = db.peer_features(&peer_id); + db.update_peer_library(&peer_id, 0, digest, features); + db.get_all_games() +} diff --git a/crates/lanspread-peer/src/services.rs b/crates/lanspread-peer/src/services.rs index a50c71e..676276e 100644 --- a/crates/lanspread-peer/src/services.rs +++ b/crates/lanspread-peer/src/services.rs @@ -1,1225 +1,18 @@ -//! Background services for the peer system. - -use std::{ - collections::{HashMap, HashSet}, - net::SocketAddr, - path::PathBuf, - sync::Arc, - time::Duration, -}; - -use futures::{SinkExt, StreamExt}; -use lanspread_db::db::Game; -use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, MdnsBrowser, MdnsService}; -use lanspread_proto::{ - Hello, - HelloAck, - LibraryDelta, - LibrarySnapshot, - Message, - PROTOCOL_VERSION, - Request, - Response, -}; -use s2n_quic::{Connection, Server, provider::limits::Limits, stream::BidirectionalStream}; -use tokio::{ - sync::{RwLock, mpsc::UnboundedSender}, - task::JoinHandle, -}; -use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec}; - -use crate::{ - PeerEvent, - config::{ - CERT_PEM, - KEY_PEM, - LOCAL_GAME_MONITOR_INTERVAL_SECS, - PEER_PING_INTERVAL_SECS, - peer_stale_timeout, - }, - context::{Ctx, PeerCtx}, - error::PeerError, - handlers::{emit_peer_game_list, update_and_announce_games}, - identity::default_features, - library::{ - LocalLibraryState, - build_library_snapshot, - build_library_summary, - compute_library_digest, - }, - local_games::{get_game_file_descriptions, scan_local_library}, - network::{ - exchange_hello, - fetch_games_from_peer, - ping_peer, - select_advertise_ip, - send_library_delta, - send_library_snapshot, - send_library_summary, - }, - peer::{send_game_file_chunk, send_game_file_data}, - peer_db::{PeerGameDB, PeerId, PeerUpsert}, -}; - -// ============================================================================= -// Server component -// ============================================================================= - -/// Runs the QUIC server and mDNS advertiser. -pub async fn run_server_component( - addr: SocketAddr, - ctx: PeerCtx, - tx_notify_ui: UnboundedSender, -) -> eyre::Result<()> { - let limits = Limits::default() - .with_max_handshake_duration(Duration::from_secs(3))? - .with_max_idle_timeout(Duration::from_secs(3))?; - - let mut server = Server::builder() - .with_tls((CERT_PEM, KEY_PEM))? - .with_io(addr)? - .with_limits(limits)? - .start()?; - - let server_addr = server.local_addr()?; - log::info!("Peer server listening on {server_addr}"); - - let advertise_ip = select_advertise_ip()?; - let advertise_addr = SocketAddr::new(advertise_ip, server_addr.port()); - log::info!("Advertising peer via mDNS from {advertise_addr}"); - { - let mut guard = ctx.local_peer_addr.write().await; - *guard = Some(advertise_addr); - } - - // Start mDNS advertising for peer discovery - let peer_id = ctx.peer_id.as_ref().clone(); - let hostname = gethostname::gethostname(); - let hostname_str = hostname.to_str().unwrap_or(""); - - // Calculate maximum hostname length that fits with UUID in 63 char limit - let max_hostname_len = 63usize.saturating_sub(peer_id.len() + 1); - let truncated_hostname = if hostname_str.len() > max_hostname_len { - hostname_str.get(..max_hostname_len).unwrap_or(hostname_str) - } else { - hostname_str - }; - - let combined_str = if truncated_hostname.is_empty() { - peer_id.clone() - } else { - format!("{truncated_hostname}-{peer_id}") - }; - - let (library_rev, library_digest) = { - let library_guard = ctx.local_library.read().await; - (library_guard.revision, library_guard.digest) - }; - - let mut properties = HashMap::new(); - properties.insert("peer_id".to_string(), peer_id.clone()); - properties.insert("proto_ver".to_string(), PROTOCOL_VERSION.to_string()); - properties.insert("library_rev".to_string(), library_rev.to_string()); - properties.insert("library_digest".to_string(), library_digest.to_string()); - if !hostname_str.is_empty() { - properties.insert("hostname".to_string(), hostname_str.to_string()); - } - - let mdns = tokio::task::spawn_blocking(move || { - MdnsAdvertiser::new( - LANSPREAD_SERVICE_TYPE, - &combined_str, - advertise_addr, - Some(properties), - ) - }) - .await??; - - // Monitor mDNS events - let _tx_notify_ui_mdns = tx_notify_ui.clone(); - let hostname = truncated_hostname.to_string(); - tokio::spawn(async move { - log::info!("Registering mDNS service with hostname: {hostname}"); - while let Ok(event) = mdns.monitor.recv() { - match event { - lanspread_mdns::DaemonEvent::Error(e) => { - log::error!("mDNS error: {e}"); - tokio::time::sleep(Duration::from_secs(1)).await; - } - _ => { - log::trace!("mDNS event: {event:?}"); - } - } - } - }); - - while let Some(connection) = server.accept().await { - let ctx = ctx.clone(); - let tx_notify_ui = tx_notify_ui.clone(); - - tokio::spawn(async move { - if let Err(e) = handle_peer_connection(connection, ctx, tx_notify_ui).await { - log::error!("Peer connection error: {e}"); - } - }); - } - - Ok(()) -} - -/// Handles an incoming peer connection. -async fn handle_peer_connection( - mut connection: Connection, - ctx: PeerCtx, - tx_notify_ui: UnboundedSender, -) -> eyre::Result<()> { - let remote_addr = connection.remote_addr()?; - log::info!("{remote_addr} peer connected"); - - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerConnected(remote_addr)) { - log::error!("Failed to send PeerConnected event: {e}"); - } - - // handle streams - while let Ok(Some(stream)) = connection.accept_bidirectional_stream().await { - let ctx = ctx.clone(); - let remote_addr = Some(remote_addr); - - tokio::spawn(async move { - if let Err(e) = handle_peer_stream(stream, ctx, remote_addr).await { - log::error!("{remote_addr:?} peer stream error: {e}"); - } - }); - } - - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerDisconnected(remote_addr)) { - log::error!("Failed to send PeerDisconnected event: {e}"); - } - - Ok(()) -} - -enum LibraryUpdate { - Delta(LibraryDelta), - Snapshot(LibrarySnapshot), -} - -async fn build_hello_from_state( - peer_id: &str, - local_library: &Arc>, -) -> Hello { - let library_guard = local_library.read().await; - Hello { - peer_id: peer_id.to_string(), - proto_ver: PROTOCOL_VERSION, - library_rev: library_guard.revision, - library_digest: library_guard.digest, - features: default_features(), - } -} - -async fn build_hello_ack(ctx: &PeerCtx) -> HelloAck { - let library_guard = ctx.local_library.read().await; - HelloAck { - peer_id: ctx.peer_id.as_ref().clone(), - proto_ver: PROTOCOL_VERSION, - library_rev: library_guard.revision, - library_digest: library_guard.digest, - features: default_features(), - } -} - -async fn select_library_update( - local_library: &Arc>, - remote_rev: u64, - remote_digest: u64, -) -> Option { - let library_guard = local_library.read().await; - if library_guard.digest == remote_digest { - return None; - } - - if remote_rev > library_guard.revision { - return None; - } - - if let Some(delta) = library_guard.delta_since(remote_rev) { - return Some(LibraryUpdate::Delta(delta)); - } - - Some(LibraryUpdate::Snapshot(build_library_snapshot( - &library_guard, - ))) -} - -async fn ensure_peer_id_for_addr( - peer_game_db: &Arc>, - peer_addr: SocketAddr, -) -> PeerId { - let mut db = peer_game_db.write().await; - if let Some(peer_id) = db.peer_id_for_addr(&peer_addr).cloned() { - return peer_id; - } - - let legacy_id = format!("legacy-{peer_addr}"); - db.upsert_peer(legacy_id.clone(), peer_addr); - legacy_id -} - -fn summary_from_game(game: &Game) -> lanspread_proto::GameSummary { - lanspread_proto::GameSummary { - id: game.id.clone(), - name: game.name.clone(), - size: game.size, - downloaded: game.downloaded, - installed: game.installed, - eti_version: game.eti_game_version.clone(), - manifest_hash: 0, - availability: lanspread_proto::Availability::Ready, - } -} - -async fn perform_handshake_with_peer( - peer_id: Arc, - local_library: Arc>, - peer_game_db: Arc>, - tx_notify_ui: UnboundedSender, - peer_addr: SocketAddr, - peer_id_hint: Option, -) -> eyre::Result<()> { - let hello = build_hello_from_state(peer_id.as_ref(), &local_library).await; - let ack = exchange_hello(peer_addr, hello).await?; - - if ack.proto_ver != PROTOCOL_VERSION { - log::warn!( - "Peer {peer_addr} uses incompatible protocol {} (expected {PROTOCOL_VERSION})", - ack.proto_ver - ); - return Ok(()); - } - - if ack.peer_id == *peer_id { - log::trace!("Ignoring handshake with self for {peer_addr}"); - return Ok(()); - } - - if let Some(expected) = peer_id_hint.as_ref() - && expected != &ack.peer_id - { - log::warn!( - "Peer {peer_addr} id mismatch: mDNS advertised {expected}, hello ack returned {}", - ack.peer_id - ); - let _ = peer_game_db.write().await.remove_peer(expected); - } - - let upsert = { - let mut db = peer_game_db.write().await; - let upsert = db.upsert_peer(ack.peer_id.clone(), peer_addr); - db.update_peer_library( - &ack.peer_id, - ack.library_rev, - ack.library_digest, - ack.features.clone(), - ); - upsert - }; - - if upsert.is_new { - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerDiscovered(peer_addr)) { - log::error!("Failed to send PeerDiscovered event: {e}"); - } - - let current_peer_count = { peer_game_db.read().await.get_peer_addresses().len() }; - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerCountUpdated(current_peer_count)) { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - - let summary = { - let library_guard = local_library.read().await; - build_library_summary(&library_guard) - }; - tokio::spawn(async move { - if let Err(e) = send_library_summary(peer_addr, summary).await { - log::warn!("Failed to send library summary to {peer_addr}: {e}"); - } - }); - } - - if let Some(update) = - select_library_update(&local_library, ack.library_rev, ack.library_digest).await - { - tokio::spawn(async move { - let result = match update { - LibraryUpdate::Delta(delta) => send_library_delta(peer_addr, delta).await, - LibraryUpdate::Snapshot(snapshot) => { - send_library_snapshot(peer_addr, snapshot).await - } - }; - - if let Err(e) = result { - log::warn!("Failed to send library update to {peer_addr}: {e}"); - } - }); - } - - Ok(()) -} - -struct MdnsPeerInfo { - addr: SocketAddr, - peer_id: Option, - proto_ver: Option, - library_rev: u64, - library_digest: u64, -} - -fn parse_mdns_peer(service: &MdnsService) -> MdnsPeerInfo { - let peer_id = service.properties.get("peer_id").cloned(); - let proto_ver = service - .properties - .get("proto_ver") - .and_then(|value| value.parse::().ok()); - let library_rev = service - .properties - .get("library_rev") - .and_then(|value| value.parse::().ok()) - .unwrap_or(0); - let library_digest = service - .properties - .get("library_digest") - .and_then(|value| value.parse::().ok()) - .unwrap_or(0); - - MdnsPeerInfo { - addr: service.addr, - peer_id, - proto_ver, - library_rev, - library_digest, - } -} - -async fn is_self_advertisement(info: &MdnsPeerInfo, ctx: &Ctx) -> bool { - let guard = ctx.local_peer_addr.read().await; - guard.as_ref().is_some_and(|addr| *addr == info.addr) - || info - .peer_id - .as_ref() - .is_some_and(|peer_id| peer_id == ctx.peer_id.as_ref()) -} - -async fn handle_discovered_peer( - info: MdnsPeerInfo, - ctx: &Ctx, - tx_notify_ui: &UnboundedSender, -) { - let peer_id = info - .peer_id - .unwrap_or_else(|| format!("legacy-{}", info.addr)); - let upsert = { - let mut db = ctx.peer_game_db.write().await; - let upsert = db.upsert_peer(peer_id.clone(), info.addr); - let features = db.peer_features(&peer_id); - if info.library_rev > 0 || info.library_digest > 0 { - db.update_peer_library(&peer_id, info.library_rev, info.library_digest, features); - } - upsert - }; - - if upsert.is_new { - log::info!("Discovered peer at: {}", info.addr); - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerDiscovered(info.addr)) { - log::error!("Failed to send PeerDiscovered event: {e}"); - } - - let current_peer_count = ctx.peer_game_db.read().await.get_peer_addresses().len(); - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerCountUpdated(current_peer_count)) { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - } - - if upsert.is_new || upsert.addr_changed { - let peer_id_arc = ctx.peer_id.clone(); - let local_library = ctx.local_library.clone(); - let peer_game_db = ctx.peer_game_db.clone(); - let tx_notify_ui_clone = tx_notify_ui.clone(); - let peer_id_hint = Some(peer_id.clone()); - - tokio::spawn(async move { - let handshake_result = - if info.proto_ver.is_none() || info.proto_ver == Some(PROTOCOL_VERSION) { - perform_handshake_with_peer( - peer_id_arc, - local_library, - peer_game_db.clone(), - tx_notify_ui_clone.clone(), - info.addr, - peer_id_hint.clone(), - ) - .await - } else { - Err(eyre::eyre!("Skipping hello for legacy peer")) - }; - - if handshake_result.is_err() - && let Err(e) = - request_games_from_peer(info.addr, tx_notify_ui_clone, peer_game_db, 0).await - { - log::error!("Failed to request games from peer {}: {e}", info.addr); - } - }); - } -} - -/// Handles a bidirectional stream from a peer. -#[allow(clippy::too_many_lines)] -async fn handle_peer_stream( - stream: BidirectionalStream, - ctx: PeerCtx, - remote_addr: Option, -) -> eyre::Result<()> { - let (rx, tx) = stream.split(); - let mut framed_rx = FramedRead::new(rx, LengthDelimitedCodec::new()); - let mut framed_tx = FramedWrite::new(tx, LengthDelimitedCodec::new()); - - log::trace!("{remote_addr:?} peer stream opened"); - - // handle streams - loop { - match framed_rx.next().await { - Some(Ok(data)) => { - log::trace!( - "{:?} msg: (raw): {}", - remote_addr, - String::from_utf8_lossy(&data) - ); - - let request = Request::decode(data.freeze()); - log::debug!("{remote_addr:?} msg: {request:?}"); - - if let Some(addr) = remote_addr { - ctx.peer_game_db - .write() - .await - .update_last_seen_by_addr(&addr); - } - - match request { - Request::Ping => { - if let Err(e) = framed_tx.send(Response::Pong.encode()).await { - log::error!("Failed to send pong: {e}"); - } - } - Request::Hello(hello) => { - if hello.peer_id == *ctx.peer_id { - log::trace!("Ignoring hello from self"); - let ack = build_hello_ack(&ctx).await; - if let Err(e) = framed_tx.send(Response::HelloAck(ack).encode()).await { - log::error!("Failed to send HelloAck: {e}"); - } - continue; - } - - if hello.proto_ver != PROTOCOL_VERSION { - log::warn!( - "Incompatible protocol from {remote_addr:?}: {}", - hello.proto_ver - ); - let ack = build_hello_ack(&ctx).await; - if let Err(e) = framed_tx.send(Response::HelloAck(ack).encode()).await { - log::error!("Failed to send HelloAck: {e}"); - } - continue; - } - - let upsert = if let Some(addr) = remote_addr { - let mut db = ctx.peer_game_db.write().await; - let upsert = db.upsert_peer(hello.peer_id.clone(), addr); - db.update_peer_library( - &hello.peer_id, - hello.library_rev, - hello.library_digest, - hello.features.clone(), - ); - upsert - } else { - PeerUpsert { - is_new: false, - addr_changed: false, - } - }; - - if upsert.is_new - && let Some(addr) = remote_addr - { - if let Err(e) = ctx.tx_notify_ui.send(PeerEvent::PeerDiscovered(addr)) { - log::error!("Failed to send PeerDiscovered event: {e}"); - } - - let current_peer_count = - { ctx.peer_game_db.read().await.get_peer_addresses().len() }; - if let Err(e) = ctx - .tx_notify_ui - .send(PeerEvent::PeerCountUpdated(current_peer_count)) - { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - } - - let ack = build_hello_ack(&ctx).await; - if let Err(e) = framed_tx.send(Response::HelloAck(ack).encode()).await { - log::error!("Failed to send HelloAck: {e}"); - } - - if let Some(addr) = remote_addr { - if upsert.is_new { - let summary = { - let library_guard = ctx.local_library.read().await; - build_library_summary(&library_guard) - }; - tokio::spawn(async move { - if let Err(e) = send_library_summary(addr, summary).await { - log::warn!("Failed to send library summary to {addr}: {e}"); - } - }); - } - - if let Some(update) = select_library_update( - &ctx.local_library, - hello.library_rev, - hello.library_digest, - ) - .await - { - tokio::spawn(async move { - let result = match update { - LibraryUpdate::Delta(delta) => { - send_library_delta(addr, delta).await - } - LibraryUpdate::Snapshot(snapshot) => { - send_library_snapshot(addr, snapshot).await - } - }; - - if let Err(e) = result { - log::warn!("Failed to send library update to {addr}: {e}"); - } - }); - } - } - } - Request::ListGames => { - log::info!("Received ListGames request from peer"); - let snapshot = { - let db_guard = ctx.local_game_db.read().await; - if let Some(ref db) = *db_guard { - db.all_games().into_iter().cloned().collect::>() - } else { - log::info!( - "Local game database not yet loaded, responding with empty game list" - ); - Vec::new() - } - }; - let games = if snapshot.is_empty() { - snapshot - } else { - let downloading = ctx.downloading_games.read().await; - snapshot - .into_iter() - .filter(|game| !downloading.contains(&game.id)) - .collect() - }; - - if let Err(e) = framed_tx.send(Response::ListGames(games).encode()).await { - log::error!("Failed to send ListGames response: {e}"); - } - } - Request::LibrarySummary(summary) => { - if let Some(addr) = remote_addr { - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; - let (previous_digest, previous_count, features) = { - let db = ctx.peer_game_db.read().await; - let (_, digest) = db.peer_library_state(&peer_id).unwrap_or((0, 0)); - ( - digest, - db.peer_game_count(&peer_id), - db.peer_features(&peer_id), - ) - }; - - { - let mut db = ctx.peer_game_db.write().await; - db.update_peer_library( - &peer_id, - summary.library_rev, - summary.library_digest, - features, - ); - } - - if summary.library_digest != previous_digest || previous_count == 0 { - let peer_id_arc = ctx.peer_id.clone(); - let local_library = ctx.local_library.clone(); - let peer_game_db = ctx.peer_game_db.clone(); - let tx_notify_ui = ctx.tx_notify_ui.clone(); - tokio::spawn(async move { - if let Err(e) = perform_handshake_with_peer( - peer_id_arc, - local_library, - peer_game_db, - tx_notify_ui, - addr, - Some(peer_id), - ) - .await - { - log::warn!("Failed to refresh library from {addr}: {e}"); - } - }); - } - } - } - Request::LibrarySnapshot(snapshot) => { - if let Some(addr) = remote_addr { - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; - { - let mut db = ctx.peer_game_db.write().await; - db.apply_library_snapshot(&peer_id, snapshot); - } - - emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; - } - } - Request::LibraryDelta(delta) => { - if let Some(addr) = remote_addr { - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; - let applied = { - let mut db = ctx.peer_game_db.write().await; - db.apply_library_delta(&peer_id, delta) - }; - - if applied { - emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; - } else { - let peer_id_arc = ctx.peer_id.clone(); - let local_library = ctx.local_library.clone(); - let peer_game_db = ctx.peer_game_db.clone(); - let tx_notify_ui = ctx.tx_notify_ui.clone(); - tokio::spawn(async move { - if let Err(e) = perform_handshake_with_peer( - peer_id_arc, - local_library, - peer_game_db, - tx_notify_ui, - addr, - Some(peer_id), - ) - .await - { - log::warn!("Failed to resync library from {addr}: {e}"); - } - }); - } - } - } - Request::GetGame { id } => { - log::info!("Received GetGame request for {id} from peer"); - let downloading = ctx.downloading_games.read().await.contains(&id); - let response = if downloading { - log::info!( - "Declining to serve GetGame for {id} because download is in progress" - ); - Response::GameNotFound(id) - } else if let Some(ref game_dir) = *ctx.game_dir.read().await { - if let Some(ref db) = *ctx.local_game_db.read().await { - if db.get_game_by_id(&id).is_some() { - match get_game_file_descriptions(&id, game_dir).await { - Ok(file_descriptions) => Response::GetGame { - id, - file_descriptions, - }, - Err(PeerError::FileSizeDetermination { path, source }) => { - let error_msg = format!( - "Failed to determine file size for {path}: {source}" - ); - log::error!( - "File size determination error for game {id}: {error_msg}" - ); - Response::InternalPeerError(error_msg) - } - Err(e) => { - log::error!( - "Failed to get game file descriptions for {id}: {e}" - ); - Response::GameNotFound(id) - } - } - } else { - Response::GameNotFound(id) - } - } else { - Response::GameNotFound(id) - } - } else { - Response::GameNotFound(id) - }; - - if let Err(e) = framed_tx.send(response.encode()).await { - log::error!("Failed to send GetGame response: {e}"); - } - } - Request::GetGameFileData(desc) => { - log::info!( - "Received GetGameFileData request for {} from peer", - desc.relative_path - ); - - let maybe_game_dir = ctx.game_dir.read().await.clone(); - if let Some(game_dir) = maybe_game_dir { - let base_dir = PathBuf::from(game_dir); - let mut tx = framed_tx.into_inner(); - send_game_file_data(&desc, &mut tx, &base_dir).await; - framed_tx = FramedWrite::new(tx, LengthDelimitedCodec::new()); - } else if let Err(e) = framed_tx - .send( - Response::InvalidRequest( - desc.relative_path.as_bytes().to_vec().into(), - "Game directory not set".to_string(), - ) - .encode(), - ) - .await - { - log::error!("Failed to send GetGameFileData error: {e}"); - } - } - Request::GetGameFileChunk { - game_id, - relative_path, - offset, - length, - } => { - log::info!( - "{remote_addr:?} received GetGameFileChunk request for {relative_path} (offset {offset}, length {length})" - ); - - let maybe_game_dir = ctx.game_dir.read().await.clone(); - if let Some(game_dir) = maybe_game_dir { - let base_dir = PathBuf::from(game_dir); - let mut tx = framed_tx.into_inner(); - send_game_file_chunk( - &game_id, - &relative_path, - offset, - length, - &mut tx, - &base_dir, - ) - .await; - framed_tx = FramedWrite::new(tx, LengthDelimitedCodec::new()); - } else if let Err(e) = framed_tx - .send( - Response::InvalidRequest( - relative_path.as_bytes().to_vec().into(), - "Game directory not set".to_string(), - ) - .encode(), - ) - .await - { - log::error!("Failed to send GetGameFileChunk error: {e}"); - } - } - Request::Goodbye { peer_id } => { - log::info!("Received Goodbye from peer {peer_id}"); - let removed = { ctx.peer_game_db.write().await.remove_peer(&peer_id) }; - if removed.is_some() { - if let Some(addr) = remote_addr { - if let Err(e) = ctx.tx_notify_ui.send(PeerEvent::PeerLost(addr)) { - log::error!("Failed to send PeerLost event: {e}"); - } - - let current_peer_count = - { ctx.peer_game_db.read().await.get_peer_addresses().len() }; - if let Err(e) = ctx - .tx_notify_ui - .send(PeerEvent::PeerCountUpdated(current_peer_count)) - { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - } - - emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; - } - } - Request::Invalid(_, _) => { - log::error!("Received invalid request from peer"); - } - Request::AnnounceGames(games) => { - log::info!( - "Received {} announced games from peer {remote_addr:?}", - games.len() - ); - if let Some(addr) = remote_addr { - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; - let summaries: Vec<_> = games.iter().map(summary_from_game).collect(); - let mut map = HashMap::with_capacity(summaries.len()); - for summary in &summaries { - map.insert(summary.id.clone(), summary.clone()); - } - let digest = compute_library_digest(&map); - let aggregated_games = { - let mut db = ctx.peer_game_db.write().await; - db.update_peer_games(&peer_id, summaries); - let features = db.peer_features(&peer_id); - db.update_peer_library(&peer_id, 0, digest, features); - db.get_all_games() - }; - - if let Err(e) = ctx - .tx_notify_ui - .send(PeerEvent::ListGames(aggregated_games)) - { - log::error!("Failed to send ListGames event: {e}"); - } - } - } - } - } - - Some(Err(e)) => { - log::error!("{remote_addr:?} peer stream error: {e}"); - break; - } - None => { - log::trace!("{remote_addr:?} peer stream closed"); - break; - } - } - } - - Ok(()) -} - -// ============================================================================= -// Peer discovery -// ============================================================================= - -/// Runs the peer discovery service using mDNS. -pub async fn run_peer_discovery(tx_notify_ui: UnboundedSender, ctx: Ctx) { - log::info!("Starting peer discovery task"); - - let service_type = LANSPREAD_SERVICE_TYPE.to_string(); - - loop { - let (service_tx, mut service_rx) = tokio::sync::mpsc::unbounded_channel(); - let service_type_clone = service_type.clone(); - - let worker_handle = tokio::task::spawn_blocking(move || -> eyre::Result<()> { - let browser = MdnsBrowser::new(&service_type_clone)?; - loop { - if let Some(service) = browser.next_service(None)? { - if service_tx.send(service).is_err() { - log::debug!("Peer discovery consumer dropped; stopping worker"); - break; - } - } else { - log::warn!("mDNS browser closed; stopping peer discovery worker"); - break; - } - } - Ok(()) - }); - - while let Some(service) = service_rx.recv().await { - let info = parse_mdns_peer(&service); - if is_self_advertisement(&info, &ctx).await { - log::trace!("Ignoring self advertisement at {}", info.addr); - continue; - } - - handle_discovered_peer(info, &ctx, &tx_notify_ui).await; - } - - match worker_handle.await { - Ok(Ok(())) => { - log::warn!("Peer discovery worker exited; restarting shortly"); - } - Ok(Err(e)) => { - log::error!("Peer discovery worker failed: {e}"); - } - Err(e) => { - log::error!("Peer discovery worker join error: {e}"); - } - } - - tokio::time::sleep(Duration::from_secs(5)).await; - } -} - -/// Requests games from a peer with retry logic. -async fn request_games_from_peer( - peer_addr: SocketAddr, - tx_notify_ui: UnboundedSender, - peer_game_db: Arc>, - mut retry_count: u32, -) -> eyre::Result<()> { - loop { - match fetch_games_from_peer(peer_addr).await { - Ok(games) => { - log::info!("Received {} games from peer {peer_addr}", games.len()); - - if games.is_empty() && retry_count < 1 { - log::info!("Received 0 games from peer {peer_addr}, scheduling retry in 5s"); - tokio::time::sleep(Duration::from_secs(5)).await; - retry_count += 1; - continue; - } - - let mut map = HashMap::with_capacity(games.len()); - let mut summaries = Vec::with_capacity(games.len()); - for game in &games { - let summary = summary_from_game(game); - map.insert(summary.id.clone(), summary.clone()); - summaries.push(summary); - } - let digest = compute_library_digest(&map); - let peer_id = ensure_peer_id_for_addr(&peer_game_db, peer_addr).await; - - let aggregated_games = { - let mut db = peer_game_db.write().await; - db.update_peer_games(&peer_id, summaries); - let features = db.peer_features(&peer_id); - db.update_peer_library(&peer_id, 0, digest, features); - db.get_all_games() - }; - - if let Err(e) = tx_notify_ui.send(PeerEvent::ListGames(aggregated_games)) { - log::error!("Failed to send ListGames event: {e}"); - } - return Ok(()); - } - Err(e) => return Err(e), - } - } -} - -// ============================================================================= -// Ping service -// ============================================================================= - -/// Runs the ping service to check peer liveness. -#[allow(clippy::too_many_lines)] -pub async fn run_ping_service( - tx_notify_ui: UnboundedSender, - peer_game_db: Arc>, - downloading_games: Arc>>, - active_downloads: Arc>>>, -) { - log::info!( - "Starting ping service ({PEER_PING_INTERVAL_SECS}s interval, \ -{}s idle threshold, {}s timeout)", - crate::config::PEER_PING_IDLE_SECS, - peer_stale_timeout().as_secs() - ); - - let mut interval = tokio::time::interval(Duration::from_secs(PEER_PING_INTERVAL_SECS)); - - loop { - interval.tick().await; - - let peer_snapshots = { peer_game_db.read().await.peer_liveness_snapshot() }; - - for (peer_id, peer_addr, last_seen) in peer_snapshots { - if last_seen.elapsed() < Duration::from_secs(crate::config::PEER_PING_IDLE_SECS) { - continue; - } - - let tx_notify_ui_clone = tx_notify_ui.clone(); - let peer_game_db_clone = peer_game_db.clone(); - let downloading_games_clone = downloading_games.clone(); - let active_downloads_clone = active_downloads.clone(); - - tokio::spawn(async move { - match ping_peer(peer_addr).await { - Ok(is_alive) => { - if is_alive { - peer_game_db_clone.write().await.update_last_seen(&peer_id); - } else { - log::warn!("Peer {peer_addr} failed ping check"); - let removed_peer = - peer_game_db_clone.write().await.remove_peer(&peer_id); - if let Some(peer) = removed_peer { - log::info!("Removed stale peer: {}", peer.addr); - if let Err(e) = - tx_notify_ui_clone.send(PeerEvent::PeerLost(peer.addr)) - { - log::error!("Failed to send PeerLost event: {e}"); - } - - let current_peer_count = - { peer_game_db_clone.read().await.get_peer_addresses().len() }; - if let Err(e) = tx_notify_ui_clone - .send(PeerEvent::PeerCountUpdated(current_peer_count)) - { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - - emit_peer_game_list(&peer_game_db_clone, &tx_notify_ui_clone).await; - handle_active_downloads_without_peers( - &peer_game_db_clone, - &downloading_games_clone, - &active_downloads_clone, - &tx_notify_ui_clone, - ) - .await; - } - } - } - Err(e) => { - log::error!("Failed to ping peer {peer_addr}: {e}"); - let removed_peer = peer_game_db_clone.write().await.remove_peer(&peer_id); - if let Some(peer) = removed_peer { - log::info!("Removed peer due to ping error: {}", peer.addr); - if let Err(e) = tx_notify_ui_clone.send(PeerEvent::PeerLost(peer.addr)) - { - log::error!("Failed to send PeerLost event: {e}"); - } - - let current_peer_count = - { peer_game_db_clone.read().await.get_peer_addresses().len() }; - if let Err(e) = tx_notify_ui_clone - .send(PeerEvent::PeerCountUpdated(current_peer_count)) - { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - - emit_peer_game_list(&peer_game_db_clone, &tx_notify_ui_clone).await; - handle_active_downloads_without_peers( - &peer_game_db_clone, - &downloading_games_clone, - &active_downloads_clone, - &tx_notify_ui_clone, - ) - .await; - } - } - } - }); - } - - // Also clean up stale peers - let stale_peers = { - peer_game_db - .read() - .await - .get_stale_peer_ids(peer_stale_timeout()) - }; - let mut removed_any = false; - for stale_peer_id in stale_peers { - let removed_peer = peer_game_db.write().await.remove_peer(&stale_peer_id); - if let Some(peer) = removed_peer { - log::info!("Removed stale peer: {}", peer.addr); - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerLost(peer.addr)) { - log::error!("Failed to send PeerLost event: {e}"); - } - - // Send updated peer count - let current_peer_count = { peer_game_db.read().await.get_peer_addresses().len() }; - if let Err(e) = tx_notify_ui.send(PeerEvent::PeerCountUpdated(current_peer_count)) { - log::error!("Failed to send PeerCountUpdated event: {e}"); - } - removed_any = true; - } - } - - if removed_any { - emit_peer_game_list(&peer_game_db, &tx_notify_ui).await; - handle_active_downloads_without_peers( - &peer_game_db, - &downloading_games, - &active_downloads, - &tx_notify_ui, - ) - .await; - } - } -} - -/// Handles downloads that no longer have peers available. -async fn handle_active_downloads_without_peers( - peer_game_db: &Arc>, - downloading_games: &Arc>>, - active_downloads: &Arc>>>, - tx_notify_ui: &UnboundedSender, -) { - let active_ids: Vec = { downloading_games.read().await.iter().cloned().collect() }; - if active_ids.is_empty() { - return; - } - - for id in active_ids { - let has_peers = { - let guard = peer_game_db.read().await; - !guard.peers_with_game(&id).is_empty() - }; - - if has_peers { - continue; - } - - let removed_from_tracking = { - let mut guard = downloading_games.write().await; - guard.remove(&id) - }; - - if !removed_from_tracking { - continue; - } - - if let Some(handle) = { active_downloads.write().await.remove(&id) } { - handle.abort(); - } - - if let Err(e) = - tx_notify_ui.send(PeerEvent::DownloadGameFilesAllPeersGone { id: id.clone() }) - { - log::error!("Failed to send DownloadGameFilesAllPeersGone event: {e}"); - } - } -} - -// ============================================================================= -// Local game monitor -// ============================================================================= - -/// Monitors the local game directory for changes. -pub async fn run_local_game_monitor(tx_notify_ui: UnboundedSender, ctx: Ctx) { - log::info!( - "Starting local game directory monitor ({LOCAL_GAME_MONITOR_INTERVAL_SECS}s interval)" - ); - - let mut interval = tokio::time::interval(Duration::from_secs(LOCAL_GAME_MONITOR_INTERVAL_SECS)); - - loop { - interval.tick().await; - - let game_dir = { - let guard = ctx.game_dir.read().await; - guard.clone() - }; - - if let Some(ref game_dir) = game_dir { - match scan_local_library(game_dir).await { - Ok(scan) => { - update_and_announce_games(&ctx, &tx_notify_ui, scan).await; - } - Err(e) => { - log::error!("Failed to scan local games directory: {e}"); - } - } - } - } -} +//! Long-running peer services. +//! +//! Each submodule owns one runtime concern. The public surface intentionally +//! stays small because `lib.rs` only needs to start these four background tasks. + +mod advertise; +mod discovery; +mod handshake; +mod legacy; +mod liveness; +mod local_monitor; +mod server; +mod stream; + +pub use discovery::run_peer_discovery; +pub use liveness::run_ping_service; +pub use local_monitor::run_local_game_monitor; +pub use server::run_server_component; diff --git a/crates/lanspread-peer/src/services/advertise.rs b/crates/lanspread-peer/src/services/advertise.rs new file mode 100644 index 0000000..620c909 --- /dev/null +++ b/crates/lanspread-peer/src/services/advertise.rs @@ -0,0 +1,91 @@ +//! mDNS advertisement for the local peer server. + +use std::{collections::HashMap, net::SocketAddr, time::Duration}; + +use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsAdvertiser}; +use lanspread_proto::PROTOCOL_VERSION; + +use crate::{context::PeerCtx, network::select_advertise_ip}; + +pub(super) async fn start_mdns_advertiser( + ctx: &PeerCtx, + server_addr: SocketAddr, +) -> eyre::Result<()> { + let advertise_ip = select_advertise_ip()?; + let advertise_addr = SocketAddr::new(advertise_ip, server_addr.port()); + log::info!("Advertising peer via mDNS from {advertise_addr}"); + + { + let mut guard = ctx.local_peer_addr.write().await; + *guard = Some(advertise_addr); + } + + let peer_id = ctx.peer_id.as_ref().clone(); + let hostname = gethostname::gethostname().to_string_lossy().into_owned(); + let advertised_name = advertised_service_name(&hostname, &peer_id); + let monitor_name = advertised_name.clone(); + let properties = advertisement_properties(ctx, &hostname, &peer_id).await; + + let mdns = tokio::task::spawn_blocking(move || { + MdnsAdvertiser::new( + LANSPREAD_SERVICE_TYPE, + &advertised_name, + advertise_addr, + Some(properties), + ) + }) + .await??; + + tokio::spawn(async move { + log::info!("Registered mDNS service with name: {monitor_name}"); + while let Ok(event) = mdns.monitor.recv() { + match event { + lanspread_mdns::DaemonEvent::Error(err) => { + log::error!("mDNS error: {err}"); + tokio::time::sleep(Duration::from_secs(1)).await; + } + _ => { + log::trace!("mDNS event: {event:?}"); + } + } + } + }); + + Ok(()) +} + +fn advertised_service_name(hostname: &str, peer_id: &str) -> String { + let max_hostname_len = 63usize.saturating_sub(peer_id.len() + 1); + let truncated_hostname = if hostname.len() > max_hostname_len { + hostname.get(..max_hostname_len).unwrap_or(hostname) + } else { + hostname + }; + + if truncated_hostname.is_empty() { + peer_id.to_string() + } else { + format!("{truncated_hostname}-{peer_id}") + } +} + +async fn advertisement_properties( + ctx: &PeerCtx, + hostname: &str, + peer_id: &str, +) -> HashMap { + let (library_rev, library_digest) = { + let library_guard = ctx.local_library.read().await; + (library_guard.revision, library_guard.digest) + }; + + let mut properties = HashMap::new(); + properties.insert("peer_id".to_string(), peer_id.to_string()); + properties.insert("proto_ver".to_string(), PROTOCOL_VERSION.to_string()); + properties.insert("library_rev".to_string(), library_rev.to_string()); + properties.insert("library_digest".to_string(), library_digest.to_string()); + if !hostname.is_empty() { + properties.insert("hostname".to_string(), hostname.to_string()); + } + properties +} diff --git a/crates/lanspread-peer/src/services/discovery.rs b/crates/lanspread-peer/src/services/discovery.rs new file mode 100644 index 0000000..6291f88 --- /dev/null +++ b/crates/lanspread-peer/src/services/discovery.rs @@ -0,0 +1,170 @@ +//! mDNS peer discovery and discovery-time protocol negotiation. + +use std::time::Duration; + +use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsBrowser, MdnsService}; +use lanspread_proto::PROTOCOL_VERSION; +use tokio::sync::mpsc::UnboundedSender; + +use crate::{ + PeerEvent, + context::Ctx, + events, + peer_db::PeerId, + services::{handshake::perform_handshake_with_peer, legacy::request_games_from_peer}, +}; + +struct MdnsPeerInfo { + addr: std::net::SocketAddr, + peer_id: Option, + proto_ver: Option, + library_rev: u64, + library_digest: u64, +} + +/// Runs the peer discovery service using mDNS. +pub async fn run_peer_discovery(tx_notify_ui: UnboundedSender, ctx: Ctx) { + log::info!("Starting peer discovery task"); + + let service_type = LANSPREAD_SERVICE_TYPE.to_string(); + + loop { + let (service_tx, mut service_rx) = tokio::sync::mpsc::unbounded_channel(); + let service_type_clone = service_type.clone(); + + let worker_handle = tokio::task::spawn_blocking(move || -> eyre::Result<()> { + let browser = MdnsBrowser::new(&service_type_clone)?; + loop { + if let Some(service) = browser.next_service(None)? { + if service_tx.send(service).is_err() { + log::debug!("Peer discovery consumer dropped; stopping worker"); + break; + } + } else { + log::warn!("mDNS browser closed; stopping peer discovery worker"); + break; + } + } + Ok(()) + }); + + while let Some(service) = service_rx.recv().await { + let info = parse_mdns_peer(&service); + if is_self_advertisement(&info, &ctx).await { + log::trace!("Ignoring self advertisement at {}", info.addr); + continue; + } + + handle_discovered_peer(info, &ctx, &tx_notify_ui).await; + } + + match worker_handle.await { + Ok(Ok(())) => { + log::warn!("Peer discovery worker exited; restarting shortly"); + } + Ok(Err(err)) => { + log::error!("Peer discovery worker failed: {err}"); + } + Err(err) => { + log::error!("Peer discovery worker join error: {err}"); + } + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } +} + +fn parse_mdns_peer(service: &MdnsService) -> MdnsPeerInfo { + MdnsPeerInfo { + addr: service.addr, + peer_id: service.properties.get("peer_id").cloned(), + proto_ver: service + .properties + .get("proto_ver") + .and_then(|value| value.parse::().ok()), + library_rev: service + .properties + .get("library_rev") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0), + library_digest: service + .properties + .get("library_digest") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0), + } +} + +async fn is_self_advertisement(info: &MdnsPeerInfo, ctx: &Ctx) -> bool { + let guard = ctx.local_peer_addr.read().await; + guard.as_ref().is_some_and(|addr| *addr == info.addr) + || info + .peer_id + .as_ref() + .is_some_and(|peer_id| peer_id == ctx.peer_id.as_ref()) +} + +async fn handle_discovered_peer( + info: MdnsPeerInfo, + ctx: &Ctx, + tx_notify_ui: &UnboundedSender, +) { + let peer_id = info + .peer_id + .clone() + .unwrap_or_else(|| format!("legacy-{}", info.addr)); + + let upsert = { + let mut db = ctx.peer_game_db.write().await; + let upsert = db.upsert_peer(peer_id.clone(), info.addr); + let features = db.peer_features(&peer_id); + if info.library_rev > 0 || info.library_digest > 0 { + db.update_peer_library(&peer_id, info.library_rev, info.library_digest, features); + } + upsert + }; + + if upsert.is_new { + log::info!("Discovered peer at: {}", info.addr); + events::emit_peer_discovered(&ctx.peer_game_db, tx_notify_ui, info.addr).await; + } + + if upsert.is_new || upsert.addr_changed { + spawn_protocol_negotiation(&info, ctx, tx_notify_ui.clone(), peer_id); + } +} + +fn spawn_protocol_negotiation( + info: &MdnsPeerInfo, + ctx: &Ctx, + tx_notify_ui: UnboundedSender, + peer_id: PeerId, +) { + let peer_addr = info.addr; + let proto_ver = info.proto_ver; + let peer_id_arc = ctx.peer_id.clone(); + let local_library = ctx.local_library.clone(); + let peer_game_db = ctx.peer_game_db.clone(); + + tokio::spawn(async move { + let handshake_result = if proto_ver.is_none() || proto_ver == Some(PROTOCOL_VERSION) { + perform_handshake_with_peer( + peer_id_arc, + local_library, + peer_game_db.clone(), + tx_notify_ui.clone(), + peer_addr, + Some(peer_id), + ) + .await + } else { + Err(eyre::eyre!("Skipping hello for legacy peer")) + }; + + if handshake_result.is_err() + && let Err(err) = request_games_from_peer(peer_addr, tx_notify_ui, peer_game_db).await + { + log::error!("Failed to request games from peer {peer_addr}: {err}"); + } + }); +} diff --git a/crates/lanspread-peer/src/services/handshake.rs b/crates/lanspread-peer/src/services/handshake.rs new file mode 100644 index 0000000..9fcbbb2 --- /dev/null +++ b/crates/lanspread-peer/src/services/handshake.rs @@ -0,0 +1,267 @@ +//! Protocol handshakes and library synchronization between peers. + +use std::{net::SocketAddr, sync::Arc}; + +use lanspread_proto::{Hello, HelloAck, LibraryDelta, LibrarySnapshot, PROTOCOL_VERSION}; +use tokio::sync::{RwLock, mpsc::UnboundedSender}; + +use crate::{ + PeerEvent, + context::PeerCtx, + events, + identity::default_features, + library::{LocalLibraryState, build_library_snapshot, build_library_summary}, + network::{exchange_hello, send_library_delta, send_library_snapshot, send_library_summary}, + peer_db::{PeerGameDB, PeerId, PeerUpsert}, +}; + +enum LibraryUpdate { + Delta(LibraryDelta), + Snapshot(LibrarySnapshot), +} + +pub(super) async fn build_hello_ack(ctx: &PeerCtx) -> HelloAck { + let library_guard = ctx.local_library.read().await; + HelloAck { + peer_id: ctx.peer_id.as_ref().clone(), + proto_ver: PROTOCOL_VERSION, + library_rev: library_guard.revision, + library_digest: library_guard.digest, + features: default_features(), + } +} + +async fn build_hello_from_state( + peer_id: &str, + local_library: &Arc>, +) -> Hello { + let library_guard = local_library.read().await; + Hello { + peer_id: peer_id.to_string(), + proto_ver: PROTOCOL_VERSION, + library_rev: library_guard.revision, + library_digest: library_guard.digest, + features: default_features(), + } +} + +pub(super) async fn perform_handshake_with_peer( + peer_id: Arc, + local_library: Arc>, + peer_game_db: Arc>, + tx_notify_ui: UnboundedSender, + peer_addr: SocketAddr, + peer_id_hint: Option, +) -> eyre::Result<()> { + let hello = build_hello_from_state(peer_id.as_ref(), &local_library).await; + let ack = exchange_hello(peer_addr, hello).await?; + + if ack.proto_ver != PROTOCOL_VERSION { + log::warn!( + "Peer {peer_addr} uses incompatible protocol {} (expected {PROTOCOL_VERSION})", + ack.proto_ver + ); + return Ok(()); + } + + if ack.peer_id == *peer_id { + log::trace!("Ignoring handshake with self for {peer_addr}"); + return Ok(()); + } + + if let Some(expected) = peer_id_hint.as_ref() + && expected != &ack.peer_id + { + log::warn!( + "Peer {peer_addr} id mismatch: mDNS advertised {expected}, hello ack returned {}", + ack.peer_id + ); + let _ = peer_game_db.write().await.remove_peer(expected); + } + + let upsert = record_remote_library( + &peer_game_db, + ack.peer_id.clone(), + peer_addr, + ack.library_rev, + ack.library_digest, + ack.features.clone(), + ) + .await; + + after_peer_library_recorded( + upsert, + peer_addr, + ack.library_rev, + ack.library_digest, + &local_library, + &peer_game_db, + &tx_notify_ui, + ) + .await; + + Ok(()) +} + +pub(super) async fn accept_inbound_hello( + ctx: &PeerCtx, + remote_addr: Option, + hello: Hello, +) -> HelloAck { + if hello.peer_id == *ctx.peer_id { + log::trace!("Ignoring hello from self"); + return build_hello_ack(ctx).await; + } + + if hello.proto_ver != PROTOCOL_VERSION { + log::warn!( + "Incompatible protocol from {remote_addr:?}: {}", + hello.proto_ver + ); + return build_hello_ack(ctx).await; + } + + if let Some(addr) = remote_addr { + let upsert = record_remote_library( + &ctx.peer_game_db, + hello.peer_id.clone(), + addr, + hello.library_rev, + hello.library_digest, + hello.features.clone(), + ) + .await; + + after_peer_library_recorded( + upsert, + addr, + hello.library_rev, + hello.library_digest, + &ctx.local_library, + &ctx.peer_game_db, + &ctx.tx_notify_ui, + ) + .await; + } + + build_hello_ack(ctx).await +} + +pub(super) fn spawn_library_resync( + peer_id: Arc, + local_library: Arc>, + peer_game_db: Arc>, + tx_notify_ui: UnboundedSender, + peer_addr: SocketAddr, + peer_id_hint: PeerId, + reason: &'static str, +) { + tokio::spawn(async move { + if let Err(err) = perform_handshake_with_peer( + peer_id, + local_library, + peer_game_db, + tx_notify_ui, + peer_addr, + Some(peer_id_hint), + ) + .await + { + log::warn!("Failed to {reason} library from {peer_addr}: {err}"); + } + }); +} + +async fn record_remote_library( + peer_game_db: &Arc>, + peer_id: PeerId, + peer_addr: SocketAddr, + library_rev: u64, + library_digest: u64, + features: Vec, +) -> PeerUpsert { + let mut db = peer_game_db.write().await; + let upsert = db.upsert_peer(peer_id.clone(), peer_addr); + db.update_peer_library(&peer_id, library_rev, library_digest, features); + upsert +} + +async fn after_peer_library_recorded( + upsert: PeerUpsert, + peer_addr: SocketAddr, + remote_library_rev: u64, + remote_library_digest: u64, + local_library: &Arc>, + peer_game_db: &Arc>, + tx_notify_ui: &UnboundedSender, +) { + if upsert.is_new { + events::emit_peer_discovered(peer_game_db, tx_notify_ui, peer_addr).await; + send_local_library_summary(peer_addr, local_library).await; + } + + send_local_library_update_if_needed( + peer_addr, + local_library, + remote_library_rev, + remote_library_digest, + ) + .await; +} + +async fn send_local_library_summary( + peer_addr: SocketAddr, + local_library: &Arc>, +) { + let summary = { + let library_guard = local_library.read().await; + build_library_summary(&library_guard) + }; + + tokio::spawn(async move { + if let Err(err) = send_library_summary(peer_addr, summary).await { + log::warn!("Failed to send library summary to {peer_addr}: {err}"); + } + }); +} + +async fn send_local_library_update_if_needed( + peer_addr: SocketAddr, + local_library: &Arc>, + remote_rev: u64, + remote_digest: u64, +) { + if let Some(update) = select_library_update(local_library, remote_rev, remote_digest).await { + tokio::spawn(async move { + let result = match update { + LibraryUpdate::Delta(delta) => send_library_delta(peer_addr, delta).await, + LibraryUpdate::Snapshot(snapshot) => { + send_library_snapshot(peer_addr, snapshot).await + } + }; + + if let Err(err) = result { + log::warn!("Failed to send library update to {peer_addr}: {err}"); + } + }); + } +} + +async fn select_library_update( + local_library: &Arc>, + remote_rev: u64, + remote_digest: u64, +) -> Option { + let library_guard = local_library.read().await; + if library_guard.digest == remote_digest || remote_rev > library_guard.revision { + return None; + } + + if let Some(delta) = library_guard.delta_since(remote_rev) { + return Some(LibraryUpdate::Delta(delta)); + } + + Some(LibraryUpdate::Snapshot(build_library_snapshot( + &library_guard, + ))) +} diff --git a/crates/lanspread-peer/src/services/legacy.rs b/crates/lanspread-peer/src/services/legacy.rs new file mode 100644 index 0000000..b3c7504 --- /dev/null +++ b/crates/lanspread-peer/src/services/legacy.rs @@ -0,0 +1,41 @@ +//! Compatibility path for peers that only support the original game-list protocol. + +use std::{net::SocketAddr, sync::Arc, time::Duration}; + +use tokio::sync::{RwLock, mpsc::UnboundedSender}; + +use crate::{ + PeerEvent, + events, + network::fetch_games_from_peer, + peer_db::PeerGameDB, + remote_peer::update_peer_from_game_list, +}; + +pub(super) async fn request_games_from_peer( + peer_addr: SocketAddr, + tx_notify_ui: UnboundedSender, + peer_game_db: Arc>, +) -> eyre::Result<()> { + let mut retry_count = 0; + + loop { + let games = fetch_games_from_peer(peer_addr).await?; + log::info!("Received {} games from peer {peer_addr}", games.len()); + + if games.is_empty() && retry_count < 1 { + log::info!("Received 0 games from peer {peer_addr}, scheduling retry in 5s"); + tokio::time::sleep(Duration::from_secs(5)).await; + retry_count += 1; + continue; + } + + let aggregated_games = update_peer_from_game_list(&peer_game_db, peer_addr, &games).await; + events::send( + &tx_notify_ui, + PeerEvent::ListGames(aggregated_games), + "ListGames", + ); + return Ok(()); + } +} diff --git a/crates/lanspread-peer/src/services/liveness.rs b/crates/lanspread-peer/src/services/liveness.rs new file mode 100644 index 0000000..a5f4ce3 --- /dev/null +++ b/crates/lanspread-peer/src/services/liveness.rs @@ -0,0 +1,223 @@ +//! Peer liveness checks and stale-peer cleanup. + +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, + time::Duration, +}; + +use tokio::{ + sync::{RwLock, mpsc::UnboundedSender}, + task::JoinHandle, +}; + +use crate::{ + PeerEvent, + config::{PEER_PING_IDLE_SECS, PEER_PING_INTERVAL_SECS, peer_stale_timeout}, + events, + network::ping_peer, + peer_db::{PeerGameDB, PeerId}, +}; + +/// Runs the ping service to check peer liveness. +pub async fn run_ping_service( + tx_notify_ui: UnboundedSender, + peer_game_db: Arc>, + downloading_games: Arc>>, + active_downloads: Arc>>>, +) { + log::info!( + "Starting ping service ({PEER_PING_INTERVAL_SECS}s interval, \ +{}s idle threshold, {}s timeout)", + PEER_PING_IDLE_SECS, + peer_stale_timeout().as_secs() + ); + + let mut interval = tokio::time::interval(Duration::from_secs(PEER_PING_INTERVAL_SECS)); + + loop { + interval.tick().await; + ping_idle_peers( + &peer_game_db, + &downloading_games, + &active_downloads, + &tx_notify_ui, + ) + .await; + + prune_stale_peers( + &peer_game_db, + &downloading_games, + &active_downloads, + &tx_notify_ui, + ) + .await; + } +} + +async fn ping_idle_peers( + peer_game_db: &Arc>, + downloading_games: &Arc>>, + active_downloads: &Arc>>>, + tx_notify_ui: &UnboundedSender, +) { + let peer_snapshots = { peer_game_db.read().await.peer_liveness_snapshot() }; + + for (peer_id, peer_addr, last_seen) in peer_snapshots { + if last_seen.elapsed() < Duration::from_secs(PEER_PING_IDLE_SECS) { + continue; + } + + let tx_notify_ui = tx_notify_ui.clone(); + let peer_game_db = peer_game_db.clone(); + let downloading_games = downloading_games.clone(); + let active_downloads = active_downloads.clone(); + + tokio::spawn(async move { + match ping_peer(peer_addr).await { + Ok(true) => { + peer_game_db.write().await.update_last_seen(&peer_id); + } + Ok(false) => { + log::warn!("Peer {peer_addr} failed ping check"); + remove_peer_and_refresh( + &peer_game_db, + &downloading_games, + &active_downloads, + &tx_notify_ui, + peer_id, + "Removed stale peer", + ) + .await; + } + Err(err) => { + log::error!("Failed to ping peer {peer_addr}: {err}"); + remove_peer_and_refresh( + &peer_game_db, + &downloading_games, + &active_downloads, + &tx_notify_ui, + peer_id, + "Removed peer due to ping error", + ) + .await; + } + } + }); + } +} + +async fn prune_stale_peers( + peer_game_db: &Arc>, + downloading_games: &Arc>>, + active_downloads: &Arc>>>, + tx_notify_ui: &UnboundedSender, +) { + let stale_peers = { + peer_game_db + .read() + .await + .get_stale_peer_ids(peer_stale_timeout()) + }; + + let mut removed_any = false; + for peer_id in stale_peers { + removed_any |= remove_peer(peer_game_db, tx_notify_ui, peer_id, "Removed stale peer").await; + } + + if removed_any { + events::emit_peer_game_list(peer_game_db, tx_notify_ui).await; + handle_active_downloads_without_peers( + peer_game_db, + downloading_games, + active_downloads, + tx_notify_ui, + ) + .await; + } +} + +async fn remove_peer_and_refresh( + peer_game_db: &Arc>, + downloading_games: &Arc>>, + active_downloads: &Arc>>>, + tx_notify_ui: &UnboundedSender, + peer_id: PeerId, + log_label: &str, +) { + if remove_peer(peer_game_db, tx_notify_ui, peer_id, log_label).await { + events::emit_peer_game_list(peer_game_db, tx_notify_ui).await; + handle_active_downloads_without_peers( + peer_game_db, + downloading_games, + active_downloads, + tx_notify_ui, + ) + .await; + } +} + +async fn remove_peer( + peer_game_db: &Arc>, + tx_notify_ui: &UnboundedSender, + peer_id: PeerId, + log_label: &str, +) -> bool { + let removed_peer = { peer_game_db.write().await.remove_peer(&peer_id) }; + let Some(peer) = removed_peer else { + return false; + }; + + log::info!("{log_label}: {}", peer.addr); + events::emit_peer_lost(peer_game_db, tx_notify_ui, peer.addr).await; + true +} + +async fn handle_active_downloads_without_peers( + peer_game_db: &Arc>, + downloading_games: &Arc>>, + active_downloads: &Arc>>>, + tx_notify_ui: &UnboundedSender, +) { + let active_ids = { + downloading_games + .read() + .await + .iter() + .cloned() + .collect::>() + }; + if active_ids.is_empty() { + return; + } + + for id in active_ids { + if peers_still_have_game(peer_game_db, &id).await { + continue; + } + + let removed_from_tracking = { + let mut guard = downloading_games.write().await; + guard.remove(&id) + }; + + if !removed_from_tracking { + continue; + } + + if let Some(handle) = { active_downloads.write().await.remove(&id) } { + handle.abort(); + } + + events::send( + tx_notify_ui, + PeerEvent::DownloadGameFilesAllPeersGone { id }, + "DownloadGameFilesAllPeersGone", + ); + } +} + +async fn peers_still_have_game(peer_game_db: &Arc>, game_id: &str) -> bool { + let guard = peer_game_db.read().await; + !guard.peers_with_game(game_id).is_empty() +} diff --git a/crates/lanspread-peer/src/services/local_monitor.rs b/crates/lanspread-peer/src/services/local_monitor.rs new file mode 100644 index 0000000..858d5de --- /dev/null +++ b/crates/lanspread-peer/src/services/local_monitor.rs @@ -0,0 +1,38 @@ +//! Local game directory monitor. + +use std::time::Duration; + +use tokio::sync::mpsc::UnboundedSender; + +use crate::{ + PeerEvent, + config::LOCAL_GAME_MONITOR_INTERVAL_SECS, + context::Ctx, + handlers::update_and_announce_games, + local_games::scan_local_library, +}; + +/// Monitors the local game directory for changes. +pub async fn run_local_game_monitor(tx_notify_ui: UnboundedSender, ctx: Ctx) { + log::info!( + "Starting local game directory monitor ({LOCAL_GAME_MONITOR_INTERVAL_SECS}s interval)" + ); + + let mut interval = tokio::time::interval(Duration::from_secs(LOCAL_GAME_MONITOR_INTERVAL_SECS)); + + loop { + interval.tick().await; + + let game_dir = { ctx.game_dir.read().await.clone() }; + if let Some(game_dir) = game_dir { + match scan_local_library(&game_dir).await { + Ok(scan) => { + update_and_announce_games(&ctx, &tx_notify_ui, scan).await; + } + Err(err) => { + log::error!("Failed to scan local games directory: {err}"); + } + } + } + } +} diff --git a/crates/lanspread-peer/src/services/server.rs b/crates/lanspread-peer/src/services/server.rs new file mode 100644 index 0000000..b0976c5 --- /dev/null +++ b/crates/lanspread-peer/src/services/server.rs @@ -0,0 +1,79 @@ +//! QUIC server accept loop. + +use std::{net::SocketAddr, time::Duration}; + +use s2n_quic::{Connection, Server, provider::limits::Limits}; +use tokio::sync::mpsc::UnboundedSender; + +use crate::{ + PeerEvent, + config::{CERT_PEM, KEY_PEM}, + context::PeerCtx, + events, + services::{advertise::start_mdns_advertiser, stream::handle_peer_stream}, +}; + +/// Runs the QUIC server and mDNS advertiser. +pub async fn run_server_component( + addr: SocketAddr, + ctx: PeerCtx, + tx_notify_ui: UnboundedSender, +) -> eyre::Result<()> { + let limits = Limits::default() + .with_max_handshake_duration(Duration::from_secs(3))? + .with_max_idle_timeout(Duration::from_secs(3))?; + + let mut server = Server::builder() + .with_tls((CERT_PEM, KEY_PEM))? + .with_io(addr)? + .with_limits(limits)? + .start()?; + + let server_addr = server.local_addr()?; + log::info!("Peer server listening on {server_addr}"); + + start_mdns_advertiser(&ctx, server_addr).await?; + + while let Some(connection) = server.accept().await { + let ctx = ctx.clone(); + let tx_notify_ui = tx_notify_ui.clone(); + + tokio::spawn(async move { + if let Err(err) = handle_peer_connection(connection, ctx, tx_notify_ui).await { + log::error!("Peer connection error: {err}"); + } + }); + } + + Ok(()) +} + +async fn handle_peer_connection( + mut connection: Connection, + ctx: PeerCtx, + tx_notify_ui: UnboundedSender, +) -> eyre::Result<()> { + let remote_addr = connection.remote_addr()?; + log::info!("{remote_addr} peer connected"); + events::send( + &tx_notify_ui, + PeerEvent::PeerConnected(remote_addr), + "PeerConnected", + ); + + while let Ok(Some(stream)) = connection.accept_bidirectional_stream().await { + let ctx = ctx.clone(); + tokio::spawn(async move { + if let Err(err) = handle_peer_stream(stream, ctx, Some(remote_addr)).await { + log::error!("{remote_addr:?} peer stream error: {err}"); + } + }); + } + + events::send( + &tx_notify_ui, + PeerEvent::PeerDisconnected(remote_addr), + "PeerDisconnected", + ); + Ok(()) +} diff --git a/crates/lanspread-peer/src/services/stream.rs b/crates/lanspread-peer/src/services/stream.rs new file mode 100644 index 0000000..4558b99 --- /dev/null +++ b/crates/lanspread-peer/src/services/stream.rs @@ -0,0 +1,397 @@ +//! Request dispatch for a single bidirectional QUIC stream. + +use std::{net::SocketAddr, path::PathBuf}; + +use futures::{SinkExt, StreamExt}; +use lanspread_db::db::{Game, GameFileDescription}; +use lanspread_proto::{LibraryDelta, LibrarySnapshot, LibrarySummary, Message, Request, Response}; +use s2n_quic::stream::{BidirectionalStream, SendStream}; +use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec}; + +use crate::{ + PeerEvent, + context::PeerCtx, + error::PeerError, + events, + local_games::get_game_file_descriptions, + peer::{send_game_file_chunk, send_game_file_data}, + remote_peer::{ensure_peer_id_for_addr, update_peer_from_game_list}, + services::handshake::{ + accept_inbound_hello, + perform_handshake_with_peer, + spawn_library_resync, + }, +}; + +type ResponseWriter = FramedWrite; + +/// Handles a bidirectional stream from a peer. +pub(super) async fn handle_peer_stream( + stream: BidirectionalStream, + ctx: PeerCtx, + remote_addr: Option, +) -> eyre::Result<()> { + let (rx, tx) = stream.split(); + let mut framed_rx = FramedRead::new(rx, LengthDelimitedCodec::new()); + let mut framed_tx = FramedWrite::new(tx, LengthDelimitedCodec::new()); + + log::trace!("{remote_addr:?} peer stream opened"); + + loop { + match framed_rx.next().await { + Some(Ok(data)) => { + log::trace!( + "{:?} msg: (raw): {}", + remote_addr, + String::from_utf8_lossy(&data) + ); + + let request = Request::decode(data.freeze()); + log::debug!("{remote_addr:?} msg: {request:?}"); + note_peer_activity(&ctx, remote_addr).await; + framed_tx = dispatch_request(&ctx, remote_addr, request, framed_tx).await; + } + Some(Err(err)) => { + log::error!("{remote_addr:?} peer stream error: {err}"); + break; + } + None => { + log::trace!("{remote_addr:?} peer stream closed"); + break; + } + } + } + + Ok(()) +} + +async fn dispatch_request( + ctx: &PeerCtx, + remote_addr: Option, + request: Request, + framed_tx: ResponseWriter, +) -> ResponseWriter { + match request { + Request::Ping => send_response(framed_tx, Response::Pong, "pong").await, + Request::Hello(hello) => { + let ack = accept_inbound_hello(ctx, remote_addr, hello).await; + send_response(framed_tx, Response::HelloAck(ack), "HelloAck").await + } + Request::ListGames => handle_list_games(ctx, framed_tx).await, + Request::LibrarySummary(summary) => { + handle_library_summary(ctx, remote_addr, summary).await; + framed_tx + } + Request::LibrarySnapshot(snapshot) => { + handle_library_snapshot(ctx, remote_addr, snapshot).await; + framed_tx + } + Request::LibraryDelta(delta) => { + handle_library_delta(ctx, remote_addr, delta).await; + framed_tx + } + Request::GetGame { id } => handle_get_game(ctx, id, framed_tx).await, + Request::GetGameFileData(desc) => handle_file_data_request(ctx, desc, framed_tx).await, + Request::GetGameFileChunk { + game_id, + relative_path, + offset, + length, + } => { + handle_file_chunk_request(ctx, game_id, relative_path, offset, length, framed_tx).await + } + Request::Goodbye { peer_id } => { + handle_goodbye(ctx, remote_addr, peer_id).await; + framed_tx + } + Request::Invalid(_, _) => { + log::error!("Received invalid request from peer"); + framed_tx + } + Request::AnnounceGames(games) => { + handle_announce_games(ctx, remote_addr, games).await; + framed_tx + } + } +} + +async fn note_peer_activity(ctx: &PeerCtx, remote_addr: Option) { + if let Some(addr) = remote_addr { + ctx.peer_game_db + .write() + .await + .update_last_seen_by_addr(&addr); + } +} + +async fn send_response( + mut framed_tx: ResponseWriter, + response: Response, + label: &str, +) -> ResponseWriter { + if let Err(err) = framed_tx.send(response.encode()).await { + log::error!("Failed to send {label} response: {err}"); + } + framed_tx +} + +async fn handle_list_games(ctx: &PeerCtx, framed_tx: ResponseWriter) -> ResponseWriter { + log::info!("Received ListGames request from peer"); + let snapshot = { + let db_guard = ctx.local_game_db.read().await; + if let Some(db) = db_guard.as_ref() { + db.all_games().into_iter().cloned().collect::>() + } else { + log::info!("Local game database not yet loaded, responding with empty game list"); + Vec::new() + } + }; + + let games = if snapshot.is_empty() { + snapshot + } else { + let downloading = ctx.downloading_games.read().await; + snapshot + .into_iter() + .filter(|game| !downloading.contains(&game.id)) + .collect() + }; + + send_response(framed_tx, Response::ListGames(games), "ListGames").await +} + +async fn handle_library_summary( + ctx: &PeerCtx, + remote_addr: Option, + summary: LibrarySummary, +) { + let Some(addr) = remote_addr else { + return; + }; + + let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; + let (previous_digest, previous_count, features) = { + let db = ctx.peer_game_db.read().await; + let (_, digest) = db.peer_library_state(&peer_id).unwrap_or((0, 0)); + ( + digest, + db.peer_game_count(&peer_id), + db.peer_features(&peer_id), + ) + }; + + { + let mut db = ctx.peer_game_db.write().await; + db.update_peer_library( + &peer_id, + summary.library_rev, + summary.library_digest, + features, + ); + } + + if summary.library_digest != previous_digest || previous_count == 0 { + tokio::spawn({ + let peer_id_arc = ctx.peer_id.clone(); + let local_library = ctx.local_library.clone(); + let peer_game_db = ctx.peer_game_db.clone(); + let tx_notify_ui = ctx.tx_notify_ui.clone(); + async move { + if let Err(err) = perform_handshake_with_peer( + peer_id_arc, + local_library, + peer_game_db, + tx_notify_ui, + addr, + Some(peer_id), + ) + .await + { + log::warn!("Failed to refresh library from {addr}: {err}"); + } + } + }); + } +} + +async fn handle_library_snapshot( + ctx: &PeerCtx, + remote_addr: Option, + snapshot: LibrarySnapshot, +) { + if let Some(addr) = remote_addr { + let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; + { + let mut db = ctx.peer_game_db.write().await; + db.apply_library_snapshot(&peer_id, snapshot); + } + + events::emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; + } +} + +async fn handle_library_delta(ctx: &PeerCtx, remote_addr: Option, delta: LibraryDelta) { + let Some(addr) = remote_addr else { + return; + }; + + let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; + let applied = { + let mut db = ctx.peer_game_db.write().await; + db.apply_library_delta(&peer_id, delta) + }; + + if applied { + events::emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; + } else { + spawn_library_resync( + ctx.peer_id.clone(), + ctx.local_library.clone(), + ctx.peer_game_db.clone(), + ctx.tx_notify_ui.clone(), + addr, + peer_id, + "resync", + ); + } +} + +async fn handle_get_game(ctx: &PeerCtx, id: String, framed_tx: ResponseWriter) -> ResponseWriter { + log::info!("Received GetGame request for {id} from peer"); + let response = get_game_response(ctx, id).await; + send_response(framed_tx, response, "GetGame").await +} + +async fn get_game_response(ctx: &PeerCtx, id: String) -> Response { + let downloading = ctx.downloading_games.read().await.contains(&id); + if downloading { + log::info!("Declining to serve GetGame for {id} because download is in progress"); + return Response::GameNotFound(id); + } + + let Some(game_dir) = ctx.game_dir.read().await.clone() else { + return Response::GameNotFound(id); + }; + + let has_game = { + let db_guard = ctx.local_game_db.read().await; + db_guard + .as_ref() + .is_some_and(|db| db.get_game_by_id(&id).is_some()) + }; + + if !has_game { + return Response::GameNotFound(id); + } + + match get_game_file_descriptions(&id, &game_dir).await { + Ok(file_descriptions) => Response::GetGame { + id, + file_descriptions, + }, + Err(PeerError::FileSizeDetermination { path, source }) => { + let error_msg = format!("Failed to determine file size for {path}: {source}"); + log::error!("File size determination error for game {id}: {error_msg}"); + Response::InternalPeerError(error_msg) + } + Err(err) => { + log::error!("Failed to get game file descriptions for {id}: {err}"); + Response::GameNotFound(id) + } + } +} + +async fn handle_file_data_request( + ctx: &PeerCtx, + desc: GameFileDescription, + framed_tx: ResponseWriter, +) -> ResponseWriter { + log::info!( + "Received GetGameFileData request for {} from peer", + desc.relative_path + ); + + let Some(game_dir) = ctx.game_dir.read().await.clone() else { + return send_invalid_request( + framed_tx, + desc.relative_path.as_bytes().to_vec(), + "Game directory not set", + ) + .await; + }; + + let base_dir = PathBuf::from(game_dir); + let mut tx = framed_tx.into_inner(); + send_game_file_data(&desc, &mut tx, &base_dir).await; + FramedWrite::new(tx, LengthDelimitedCodec::new()) +} + +async fn handle_file_chunk_request( + ctx: &PeerCtx, + game_id: String, + relative_path: String, + offset: u64, + length: u64, + framed_tx: ResponseWriter, +) -> ResponseWriter { + log::info!( + "Received GetGameFileChunk request for {relative_path} (offset {offset}, length {length})" + ); + + let Some(game_dir) = ctx.game_dir.read().await.clone() else { + return send_invalid_request( + framed_tx, + relative_path.as_bytes().to_vec(), + "Game directory not set", + ) + .await; + }; + + let base_dir = PathBuf::from(game_dir); + let mut tx = framed_tx.into_inner(); + send_game_file_chunk(&game_id, &relative_path, offset, length, &mut tx, &base_dir).await; + FramedWrite::new(tx, LengthDelimitedCodec::new()) +} + +async fn send_invalid_request( + framed_tx: ResponseWriter, + raw_request: Vec, + message: &str, +) -> ResponseWriter { + send_response( + framed_tx, + Response::InvalidRequest(raw_request.into(), message.to_string()), + "InvalidRequest", + ) + .await +} + +async fn handle_goodbye(ctx: &PeerCtx, remote_addr: Option, peer_id: String) { + log::info!("Received Goodbye from peer {peer_id}"); + let removed = { ctx.peer_game_db.write().await.remove_peer(&peer_id) }; + if removed.is_none() { + return; + } + + if let Some(addr) = remote_addr { + events::emit_peer_lost(&ctx.peer_game_db, &ctx.tx_notify_ui, addr).await; + } + + events::emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; +} + +async fn handle_announce_games(ctx: &PeerCtx, remote_addr: Option, games: Vec) { + log::info!( + "Received {} announced games from peer {remote_addr:?}", + games.len() + ); + + if let Some(addr) = remote_addr { + let aggregated_games = update_peer_from_game_list(&ctx.peer_game_db, addr, &games).await; + events::send( + &ctx.tx_notify_ui, + PeerEvent::ListGames(aggregated_games), + "ListGames", + ); + } +} diff --git a/crates/lanspread-proto/Cargo.toml b/crates/lanspread-proto/Cargo.toml index 2ceab7d..2b1cf57 100644 --- a/crates/lanspread-proto/Cargo.toml +++ b/crates/lanspread-proto/Cargo.toml @@ -20,3 +20,7 @@ bytes = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tracing = { workspace = true } + +[lib] +test = false +doctest = false diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml b/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml index 77eb535..6f7bc93 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml +++ b/crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml @@ -13,6 +13,8 @@ edition = "2024" # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 name = "lanspread_tauri_deno_ts_lib" crate-type = ["staticlib", "cdylib", "rlib"] +test = false +doctest = false [lints.clippy] pedantic = { level = "warn", priority = -1 } @@ -27,7 +29,6 @@ tauri-build = { version = "2", features = [] } # local lanspread-peer = { path = "../../lanspread-peer" } lanspread-db = { path = "../../lanspread-db" } -lanspread-mdns = { path = "../../lanspread-mdns" } lanspread-compat = { path = "../../lanspread-compat" } # external @@ -35,8 +36,6 @@ base64 = { workspace = true } eyre = { workspace = true } log = { workspace = true } mimalloc = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } tauri = { workspace = true } tauri-plugin-log = { workspace = true } tauri-plugin-shell = { workspace = true } 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 87010b1..5b41aed 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs +++ b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs @@ -895,7 +895,7 @@ pub fn run() { log::info!("Peer system initialized successfully with games directory"); // Wait a moment for local game database to be loaded before starting discovery - tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; // Start peer discovery and request games from other peers if let Err(e) = request_games(state).await { diff --git a/crates/lanspread-utils/Cargo.toml b/crates/lanspread-utils/Cargo.toml index da935d5..6e1a37a 100644 --- a/crates/lanspread-utils/Cargo.toml +++ b/crates/lanspread-utils/Cargo.toml @@ -11,4 +11,6 @@ pedantic = { level = "warn", priority = -1 } todo = "warn" unwrap_used = "warn" -[dependencies] +[lib] +test = false +doctest = false