ChatGPT Codex 5.5 xhigh refactored even more

This commit is contained in:
2026-05-02 15:31:37 +02:00
parent 86d0f93ede
commit b4585b663a
24 changed files with 2160 additions and 1972 deletions
Generated
+681 -685
View File
@@ -77,9 +77,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.101" version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
@@ -133,9 +133,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.15.4" version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
dependencies = [ dependencies = [
"aws-lc-sys", "aws-lc-sys",
"untrusted 0.7.1", "untrusted 0.7.1",
@@ -144,9 +144,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-sys" name = "aws-lc-sys"
version = "0.37.1" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
dependencies = [ dependencies = [
"cc", "cc",
"cmake", "cmake",
@@ -166,6 +166,21 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 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]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -174,9 +189,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@@ -213,25 +228,26 @@ dependencies = [
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"bytes",
"cfg_aliases", "cfg_aliases",
] ]
[[package]] [[package]]
name = "borsh-derive" name = "borsh-derive"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"proc-macro-crate 3.4.0", "proc-macro-crate 3.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -257,9 +273,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.1" version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]] [[package]]
name = "byte-unit" name = "byte-unit"
@@ -322,7 +338,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cairo-sys-rs", "cairo-sys-rs",
"glib", "glib",
"libc", "libc",
@@ -385,9 +401,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.56" version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -435,10 +451,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chacha20"
version = "0.4.43" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"num-traits", "num-traits",
@@ -448,9 +475,9 @@ dependencies = [
[[package]] [[package]]
name = "cmake" name = "cmake"
version = "0.1.57" version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@@ -474,12 +501,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "cookie" name = "cookie"
version = "0.18.1" version = "0.18.1"
@@ -508,11 +529,11 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "core-graphics" name = "core-graphics"
version = "0.24.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"core-foundation", "core-foundation",
"core-graphics-types", "core-graphics-types",
"foreign-types", "foreign-types",
@@ -525,7 +546,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"core-foundation", "core-foundation",
"libc", "libc",
] ]
@@ -539,6 +560,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc" name = "crc"
version = "3.4.0" version = "3.4.0"
@@ -550,9 +580,9 @@ dependencies = [
[[package]] [[package]]
name = "crc-catalog" name = "crc-catalog"
version = "2.4.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
@@ -599,19 +629,15 @@ dependencies = [
[[package]] [[package]]
name = "cssparser" name = "cssparser"
version = "0.29.6" version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2"
dependencies = [ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"matches", "phf 0.13.1",
"phf 0.10.1",
"proc-macro2",
"quote",
"smallvec", "smallvec",
"syn 1.0.109",
] ]
[[package]] [[package]]
@@ -621,19 +647,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.2.9" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
dependencies = [ dependencies = [
"quote", "ctor-proc-macro",
"syn 2.0.116", "dtor",
] ]
[[package]]
name = "ctor-proc-macro"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
[[package]] [[package]]
name = "cuckoofilter" name = "cuckoofilter"
version = "0.5.0" version = "0.5.0"
@@ -647,9 +679,9 @@ dependencies = [
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.21.3" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"darling_macro", "darling_macro",
@@ -657,34 +689,44 @@ dependencies = [
[[package]] [[package]]
name = "darling_core" name = "darling_core"
version = "0.21.3" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
dependencies = [ dependencies = [
"fnv",
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.21.3" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "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]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.6" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde_core", "serde_core",
@@ -692,15 +734,23 @@ dependencies = [
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.20" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"convert_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -734,19 +784,13 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "dispatch2" name = "dispatch2"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2", "block2",
"libc", "libc",
"objc2", "objc2",
@@ -760,7 +804,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -783,7 +827,22 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "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]] [[package]]
@@ -816,6 +875,21 @@ dependencies = [
"dtoa", "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]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@@ -839,14 +913,14 @@ dependencies = [
[[package]] [[package]]
name = "embed-resource" name = "embed-resource"
version = "3.0.6" version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb"
dependencies = [ dependencies = [
"cc", "cc",
"memchr", "memchr",
"rustc_version", "rustc_version",
"toml 0.9.12+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
"vswhom", "vswhom",
"winreg", "winreg",
] ]
@@ -884,9 +958,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "erased-serde" name = "erased-serde"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
dependencies = [ dependencies = [
"serde", "serde",
"serde_core", "serde_core",
@@ -926,9 +1000,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]] [[package]]
name = "fdeflate" name = "fdeflate"
@@ -1021,7 +1095,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1051,16 +1125,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 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]] [[package]]
name = "futures" name = "futures"
version = "0.3.32" version = "0.3.32"
@@ -1128,7 +1192,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1160,15 +1224,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "gdk" name = "gdk"
version = "0.18.2" version = "0.18.2"
@@ -1318,19 +1373,20 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi", "r-efi 5.3.0",
"wasip2", "wasip2",
] ]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi", "r-efi 6.0.0",
"rand_core 0.10.1",
"wasip2", "wasip2",
"wasip3", "wasip3",
] ]
@@ -1373,7 +1429,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor", "futures-executor",
@@ -1401,7 +1457,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1480,7 +1536,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -1511,9 +1567,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -1555,14 +1611,12 @@ checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.29.1" version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
dependencies = [ dependencies = [
"log", "log",
"mac",
"markup5ever", "markup5ever",
"match_token",
] ]
[[package]] [[package]]
@@ -1606,9 +1660,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -1619,7 +1673,6 @@ dependencies = [
"httparse", "httparse",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"pin-utils",
"smallvec", "smallvec",
"tokio", "tokio",
"want", "want",
@@ -1679,17 +1732,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"png", "png 0.17.16",
] ]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"potential_utf", "potential_utf",
"utf8_iter",
"yoke", "yoke",
"zerofrom", "zerofrom",
"zerovec", "zerovec",
@@ -1697,9 +1751,9 @@ dependencies = [
[[package]] [[package]]
name = "icu_locale_core" name = "icu_locale_core"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"litemap", "litemap",
@@ -1710,9 +1764,9 @@ dependencies = [
[[package]] [[package]]
name = "icu_normalizer" name = "icu_normalizer"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [ dependencies = [
"icu_collections", "icu_collections",
"icu_normalizer_data", "icu_normalizer_data",
@@ -1724,15 +1778,15 @@ dependencies = [
[[package]] [[package]]
name = "icu_normalizer_data" name = "icu_normalizer_data"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]] [[package]]
name = "icu_properties" name = "icu_properties"
version = "2.1.2" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [ dependencies = [
"icu_collections", "icu_collections",
"icu_locale_core", "icu_locale_core",
@@ -1744,15 +1798,15 @@ dependencies = [
[[package]] [[package]]
name = "icu_properties_data" name = "icu_properties_data"
version = "2.1.2" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]] [[package]]
name = "icu_provider" name = "icu_provider"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"icu_locale_core", "icu_locale_core",
@@ -1788,9 +1842,9 @@ dependencies = [
[[package]] [[package]]
name = "idna_adapter" name = "idna_adapter"
version = "1.2.1" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [ dependencies = [
"icu_normalizer", "icu_normalizer",
"icu_properties", "icu_properties",
@@ -1825,12 +1879,12 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.0" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"serde", "serde",
"serde_core", "serde_core",
] ]
@@ -1855,15 +1909,15 @@ dependencies = [
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.11.0" version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]] [[package]]
name = "iri-string" name = "iri-string"
version = "0.7.10" version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [ dependencies = [
"memchr", "memchr",
"serde", "serde",
@@ -1890,9 +1944,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "javascriptcore-rs" name = "javascriptcore-rs"
@@ -1926,7 +1980,7 @@ dependencies = [
"cesu8", "cesu8",
"cfg-if", "cfg-if",
"combine", "combine",
"jni-sys", "jni-sys 0.3.1",
"log", "log",
"thiserror 1.0.69", "thiserror 1.0.69",
"walkdir", "walkdir",
@@ -1935,9 +1989,31 @@ dependencies = [
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "jobserver" name = "jobserver"
@@ -1951,10 +2027,12 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.85" version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
dependencies = [ dependencies = [
"cfg-if",
"futures-util",
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -1987,23 +2065,11 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"serde", "serde",
"unicode-segmentation", "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]] [[package]]
name = "lanspread-compat" name = "lanspread-compat"
version = "0.1.0" version = "0.1.0"
@@ -2019,9 +2085,7 @@ dependencies = [
name = "lanspread-db" name = "lanspread-db"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytes",
"eyre", "eyre",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"tracing", "tracing",
@@ -2034,8 +2098,6 @@ dependencies = [
"eyre", "eyre",
"log", "log",
"mdns-sd", "mdns-sd",
"tokio",
"tracing",
] ]
[[package]] [[package]]
@@ -2047,7 +2109,6 @@ dependencies = [
"futures", "futures",
"gethostname", "gethostname",
"if-addrs", "if-addrs",
"lanspread-compat",
"lanspread-db", "lanspread-db",
"lanspread-mdns", "lanspread-mdns",
"lanspread-proto", "lanspread-proto",
@@ -2058,7 +2119,6 @@ dependencies = [
"serde_json", "serde_json",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing",
"uuid", "uuid",
"walkdir", "walkdir",
] ]
@@ -2082,12 +2142,9 @@ dependencies = [
"eyre", "eyre",
"lanspread-compat", "lanspread-compat",
"lanspread-db", "lanspread-db",
"lanspread-mdns",
"lanspread-peer", "lanspread-peer",
"log", "log",
"mimalloc", "mimalloc",
"serde",
"serde_json",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-dialog", "tauri-plugin-dialog",
@@ -2141,9 +2198,18 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.182" version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "libloading" name = "libloading"
@@ -2163,21 +2229,19 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
version = "0.1.44" version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6"
dependencies = [ dependencies = [
"cc", "cc",
"libc",
] ]
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.12" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"bitflags 2.11.0",
"libc", "libc",
] ]
@@ -2194,15 +2258,15 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.11.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.1" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -2222,48 +2286,22 @@ dependencies = [
"value-bag", "value-bag",
] ]
[[package]]
name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.14.1" version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862"
dependencies = [ dependencies = [
"log", "log",
"phf 0.11.3",
"phf_codegen 0.11.3",
"string_cache",
"string_cache_codegen",
"tendril", "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]] [[package]]
name = "mdns-sd" name = "mdns-sd"
version = "0.18.0" version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c23fd301a08742925d79fd29bba538c9cb0408d67907905f8f9d4e5cd8bc816" checksum = "d797ab3274a16f4940f9650a29838e940223aeff31773df5c2827ad82150182f"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"flume", "flume",
@@ -2291,9 +2329,9 @@ dependencies = [
[[package]] [[package]]
name = "mimalloc" name = "mimalloc"
version = "0.1.48" version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640"
dependencies = [ dependencies = [
"libmimalloc-sys", "libmimalloc-sys",
] ]
@@ -2316,9 +2354,9 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
@@ -2328,9 +2366,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.17.1" version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" checksum = "0ae8844f63b5b118e334e205585b8c5c17b984121dbdb179d44aeb087ffad3cb"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@@ -2341,10 +2379,10 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation", "objc2-foundation",
"once_cell", "once_cell",
"png", "png 0.18.1",
"serde", "serde",
"thiserror 2.0.18", "thiserror 2.0.18",
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -2353,8 +2391,8 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"jni-sys", "jni-sys 0.3.1",
"log", "log",
"ndk-sys", "ndk-sys",
"num_enum", "num_enum",
@@ -2362,19 +2400,13 @@ dependencies = [
"thiserror 1.0.69", "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]] [[package]]
name = "ndk-sys" name = "ndk-sys"
version = "0.6.0+11769913" version = "0.6.0+11769913"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
dependencies = [ dependencies = [
"jni-sys", "jni-sys 0.3.1",
] ]
[[package]] [[package]]
@@ -2383,17 +2415,11 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]] [[package]]
name = "num-integer" name = "num-integer"
@@ -2426,9 +2452,9 @@ dependencies = [
[[package]] [[package]]
name = "num_enum" name = "num_enum"
version = "0.7.5" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
dependencies = [ dependencies = [
"num_enum_derive", "num_enum_derive",
"rustversion", "rustversion",
@@ -2436,14 +2462,14 @@ dependencies = [
[[package]] [[package]]
name = "num_enum_derive" name = "num_enum_derive"
version = "0.7.5" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -2457,9 +2483,9 @@ dependencies = [
[[package]] [[package]]
name = "objc2" name = "objc2"
version = "0.6.3" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [ dependencies = [
"objc2-encode", "objc2-encode",
"objc2-exception-helper", "objc2-exception-helper",
@@ -2471,19 +2497,11 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2", "block2",
"libc",
"objc2", "objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-text",
"objc2-core-video",
"objc2-foundation", "objc2-foundation",
"objc2-quartz-core",
] ]
[[package]] [[package]]
@@ -2492,7 +2510,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"objc2", "objc2",
"objc2-foundation", "objc2-foundation",
] ]
@@ -2503,7 +2521,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [ dependencies = [
"bitflags 2.11.0",
"objc2", "objc2",
"objc2-foundation", "objc2-foundation",
] ]
@@ -2514,7 +2531,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"dispatch2", "dispatch2",
"objc2", "objc2",
] ]
@@ -2525,7 +2542,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"dispatch2", "dispatch2",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
@@ -2542,31 +2559,28 @@ dependencies = [
"objc2-foundation", "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]] [[package]]
name = "objc2-core-text" name = "objc2-core-text"
version = "0.3.2" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "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]] [[package]]
name = "objc2-encode" name = "objc2-encode"
version = "4.1.0" version = "4.1.0"
@@ -2588,7 +2602,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2", "block2",
"libc", "libc",
"objc2", "objc2",
@@ -2601,17 +2615,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"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 = [
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -2622,32 +2626,40 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-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]] [[package]]
name = "objc2-ui-kit" name = "objc2-ui-kit"
version = "0.3.2" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2",
"objc2", "objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation", "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", "objc2-foundation",
] ]
@@ -2657,27 +2669,25 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2", "block2",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation", "objc2-foundation",
"objc2-javascript-core",
"objc2-security",
] ]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]] [[package]]
name = "open" name = "open"
version = "5.3.3" version = "5.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd"
dependencies = [ dependencies = [
"dunce", "dunce",
"is-wsl", "is-wsl",
@@ -2767,26 +2777,6 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 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]] [[package]]
name = "phf" name = "phf"
version = "0.11.3" version = "0.11.3"
@@ -2798,43 +2788,24 @@ dependencies = [
] ]
[[package]] [[package]]
name = "phf_codegen" name = "phf"
version = "0.8.0" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [ dependencies = [
"phf_generator 0.8.0", "phf_macros 0.13.1",
"phf_shared 0.8.0", "phf_shared 0.13.1",
"serde",
] ]
[[package]] [[package]]
name = "phf_codegen" name = "phf_codegen"
version = "0.11.3" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
dependencies = [ dependencies = [
"phf_generator 0.11.3", "phf_generator 0.13.1",
"phf_shared 0.11.3", "phf_shared 0.13.1",
]
[[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",
] ]
[[package]] [[package]]
@@ -2844,21 +2815,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [ dependencies = [
"phf_shared 0.11.3", "phf_shared 0.11.3",
"rand 0.8.5", "rand 0.8.6",
] ]
[[package]] [[package]]
name = "phf_macros" name = "phf_generator"
version = "0.10.0" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [ dependencies = [
"phf_generator 0.10.0", "fastrand",
"phf_shared 0.10.0", "phf_shared 0.13.1",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
] ]
[[package]] [[package]]
@@ -2871,25 +2838,20 @@ dependencies = [
"phf_shared 0.11.3", "phf_shared 0.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "phf_shared" name = "phf_macros"
version = "0.8.0" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
dependencies = [ dependencies = [
"siphasher 0.3.11", "phf_generator 0.13.1",
] "phf_shared 0.13.1",
"proc-macro2",
[[package]] "quote",
name = "phf_shared" "syn 2.0.117",
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher 0.3.11",
] ]
[[package]] [[package]]
@@ -2898,35 +2860,38 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [ 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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.32" version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]] [[package]]
name = "plist" name = "plist"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"indexmap 2.13.0", "indexmap 2.14.0",
"quick-xml", "quick-xml",
"serde", "serde",
"time", "time",
@@ -2946,10 +2911,23 @@ dependencies = [
] ]
[[package]] [[package]]
name = "potential_utf" name = "png"
version = "0.1.4" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"zerovec", "zerovec",
] ]
@@ -2982,7 +2960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3007,11 +2985,11 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "3.4.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [ dependencies = [
"toml_edit 0.23.10+spec-1.0.0", "toml_edit 0.25.11+spec-1.1.0",
] ]
[[package]] [[package]]
@@ -3038,12 +3016,6 @@ dependencies = [
"version_check", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -3075,18 +3047,18 @@ dependencies = [
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.4" version = "0.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.44" version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -3097,6 +3069,12 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]] [[package]]
name = "radium" name = "radium"
version = "0.7.0" version = "0.7.0"
@@ -3114,14 +3092,13 @@ dependencies = [
"rand_chacha 0.2.2", "rand_chacha 0.2.2",
"rand_core 0.5.1", "rand_core 0.5.1",
"rand_hc", "rand_hc",
"rand_pcg",
] ]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha 0.3.1", "rand_chacha 0.3.1",
@@ -3130,12 +3107,13 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.2" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [ dependencies = [
"rand_chacha 0.9.0", "chacha20",
"rand_core 0.9.5", "getrandom 0.4.2",
"rand_core 0.10.1",
] ]
[[package]] [[package]]
@@ -3158,16 +3136,6 @@ dependencies = [
"rand_core 0.6.4", "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]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@@ -3188,12 +3156,9 @@ dependencies = [
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.9.5" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "rand_hc" name = "rand_hc"
@@ -3204,15 +3169,6 @@ dependencies = [
"rand_core 0.5.1", "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]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.6.2" version = "0.6.2"
@@ -3225,7 +3181,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
] ]
[[package]] [[package]]
@@ -3256,7 +3212,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3284,9 +3240,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.9" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "rend" name = "rend"
@@ -3299,9 +3255,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.2" version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -3400,20 +3356,27 @@ dependencies = [
[[package]] [[package]]
name = "rust_decimal" name = "rust_decimal"
version = "1.40.0" version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"borsh", "borsh",
"bytes", "bytes",
"num-traits", "num-traits",
"rand 0.8.5", "rand 0.8.6",
"rkyv", "rkyv",
"serde", "serde",
"serde_json", "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]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@@ -3425,11 +3388,11 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.3" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@@ -3438,9 +3401,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.36" version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"log", "log",
@@ -3453,18 +3416,18 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.14.0" version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [ dependencies = [
"zeroize", "zeroize",
] ]
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.9" version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"ring", "ring",
@@ -3486,9 +3449,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]] [[package]]
name = "s2n-codec" name = "s2n-codec"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37a07f4fac48aa62fe8e70ee05eb8671d143689cbb950ad6cca2e7e52313a093" checksum = "22bacdd8e47b929c10802a93070890d26b8c0e510857113b8f098fca002aecf4"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
@@ -3497,17 +3460,16 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic" name = "s2n-quic"
version = "1.74.0" version = "1.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab472129b56875c4e42e055903ce95523291a142223b331d1544e3dd9a702293" checksum = "82c23e80ef5205e7f197f6fb0922f3f66eb6599fd826cbf5eb0dc0edba17aee5"
dependencies = [ dependencies = [
"bytes", "bytes",
"cfg-if", "cfg-if",
"cuckoofilter", "cuckoofilter",
"futures", "futures",
"hash_hasher", "hash_hasher",
"rand 0.9.2", "rand 0.10.1",
"rand_chacha 0.9.0",
"s2n-codec", "s2n-codec",
"s2n-quic-core", "s2n-quic-core",
"s2n-quic-crypto", "s2n-quic-crypto",
@@ -3522,9 +3484,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-core" name = "s2n-quic-core"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f526ee912db4354dffc6229d283ac9d7cf297ec1bee8e495b786a7e520138b2" checksum = "8b0d2cd0e789568ac5c374ea7fbf599e1a9d63362bd37baf2d45d386f5f3d25f"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"byteorder", "byteorder",
@@ -3544,9 +3506,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-crypto" name = "s2n-quic-crypto"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aec6bbed663012d824ee279833959800837453a32d118d351d971da2ffc0f363" checksum = "d0b6fdfaca6213006dc5163ad8788010f6b5861e6e41352b0a3b00e0fc0930d4"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"cfg-if", "cfg-if",
@@ -3558,9 +3520,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-platform" name = "s2n-quic-platform"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7229151d4a48d17cec48d274c0c74c82220ddef932aae91fdc18d9beab7097bc" checksum = "7c4742176bbbc522d78e415dfdd3fba84b09ce4744bce20b2f7a6e8ff0c62a3a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures", "futures",
@@ -3573,9 +3535,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-rustls" name = "s2n-quic-rustls"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fff17e3c7bbde694b1cadacfb42aae93d43d57dbc77b726bdbd67518e51b2dd" checksum = "201a367dbdadc85c9e1fda1be03205740bf2cc848f9436189f88d9b7f3807554"
dependencies = [ dependencies = [
"bytes", "bytes",
"rustls", "rustls",
@@ -3587,9 +3549,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-tls" name = "s2n-quic-tls"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c35e1159a13fd9602905ea0e0c4c0a7d023d62680b8ea1ff73f9c1de1070f6" checksum = "5368d27604ea26aab1dac1df1a84b676a519e139d7927d9afbd6a8137c9129b7"
dependencies = [ dependencies = [
"bytes", "bytes",
"errno", "errno",
@@ -3602,9 +3564,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-tls-default" name = "s2n-quic-tls-default"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c14d35fee2f8997241176e7a191d11cd928e0ff6638bd6aafddf4f3d8d24ff6c" checksum = "e710c8e03558c1eaca90356f34821f6d3b01bf4bf9cc439935cb3002eab2e1d4"
dependencies = [ dependencies = [
"s2n-quic-rustls", "s2n-quic-rustls",
"s2n-quic-tls", "s2n-quic-tls",
@@ -3612,27 +3574,27 @@ dependencies = [
[[package]] [[package]]
name = "s2n-quic-transport" name = "s2n-quic-transport"
version = "0.74.0" version = "0.78.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5627b2561dba83b25908d49afa87ff27afd6f04a4cb3679731349db71967dc16" checksum = "08db5b4830452c1fff9c9fa93a3fa030c1d7d38a9fc96815e0df98e407fae612"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"intrusive-collections", "intrusive-collections",
"once_cell", "once_cell",
"s2n-codec", "s2n-codec",
"s2n-quic-core", "s2n-quic-core",
"siphasher 1.0.2", "siphasher",
"smallvec", "smallvec",
] ]
[[package]] [[package]]
name = "s2n-tls" name = "s2n-tls"
version = "0.3.34" version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ed0b0786afe33ad792aadf289629d39cb45edc7ab1250a6b6cdf71ebc1c026" checksum = "5d22c36f207d0bb6a38272d5d1fa1cd99094335c70db1bfef54e0b8b672ae26f"
dependencies = [ dependencies = [
"errno", "errno",
"hex", "hex",
@@ -3643,9 +3605,9 @@ dependencies = [
[[package]] [[package]]
name = "s2n-tls-sys" name = "s2n-tls-sys"
version = "0.3.34" version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1333e7ea5fbc8f36ef4c3c4aa0269441195cee56bef783f0d418151eebb9eac" checksum = "bcf9e3b136cdd2c99f0cee2811d80b9f6bc44b5fd48c172857de32babb506f5e"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"cc", "cc",
@@ -3709,7 +3671,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3726,27 +3688,28 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.24.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.11.1",
"cssparser", "cssparser",
"derive_more", "derive_more",
"fxhash",
"log", "log",
"phf 0.8.0", "new_debug_unreachable",
"phf_codegen 0.8.0", "phf 0.13.1",
"phf_codegen",
"precomputed-hash", "precomputed-hash",
"rustc-hash",
"servo_arc", "servo_arc",
"smallvec", "smallvec",
] ]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
dependencies = [ dependencies = [
"serde", "serde",
"serde_core", "serde_core",
@@ -3791,7 +3754,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3802,7 +3765,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3826,7 +3789,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3840,9 +3803,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.4" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@@ -3861,15 +3824,15 @@ dependencies = [
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.16.1" version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"chrono", "chrono",
"hex", "hex",
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.13.0", "indexmap 2.14.0",
"schemars 0.9.0", "schemars 0.9.0",
"schemars 1.2.1", "schemars 1.2.1",
"serde_core", "serde_core",
@@ -3880,14 +3843,14 @@ dependencies = [
[[package]] [[package]]
name = "serde_with_macros" name = "serde_with_macros"
version = "3.16.1" version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -3909,16 +3872,15 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "servo_arc" name = "servo_arc"
version = "0.2.0" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930"
dependencies = [ dependencies = [
"nodrop",
"stable_deref_trait", "stable_deref_trait",
] ]
@@ -3929,7 +3891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures 0.2.17",
"digest", "digest",
] ]
@@ -3983,9 +3945,9 @@ dependencies = [
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.8" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]] [[package]]
name = "simdutf8" name = "simdutf8"
@@ -3993,12 +3955,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.2" version = "1.0.2"
@@ -4030,12 +3986,12 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -4124,7 +4080,7 @@ dependencies = [
"futures-util", "futures-util",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"hashlink", "hashlink",
"indexmap 2.13.0", "indexmap 2.14.0",
"log", "log",
"memchr", "memchr",
"once_cell", "once_cell",
@@ -4149,7 +4105,7 @@ dependencies = [
"quote", "quote",
"sqlx-core", "sqlx-core",
"sqlx-macros-core", "sqlx-macros-core",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4170,7 +4126,7 @@ dependencies = [
"sha2", "sha2",
"sqlx-core", "sqlx-core",
"sqlx-sqlite", "sqlx-sqlite",
"syn 2.0.116", "syn 2.0.117",
"tokio", "tokio",
"url", "url",
] ]
@@ -4207,25 +4163,24 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.9" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
dependencies = [ dependencies = [
"new_debug_unreachable", "new_debug_unreachable",
"parking_lot", "parking_lot",
"phf_shared 0.11.3", "phf_shared 0.13.1",
"precomputed-hash", "precomputed-hash",
"serde",
] ]
[[package]] [[package]]
name = "string_cache_codegen" name = "string_cache_codegen"
version = "0.5.4" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
dependencies = [ dependencies = [
"phf_generator 0.11.3", "phf_generator 0.13.1",
"phf_shared 0.11.3", "phf_shared 0.13.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
] ]
@@ -4266,9 +4221,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.116" version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4292,7 +4247,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4310,35 +4265,35 @@ dependencies = [
[[package]] [[package]]
name = "tao" name = "tao"
version = "0.34.5" version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" checksum = "1cf65722394c2ac443e80120064987f8914ee1d4e4e36e63cdf10f2990f01159"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2", "block2",
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
"dispatch", "dbus",
"dispatch2",
"dlopen2", "dlopen2",
"dpi", "dpi",
"gdkwayland-sys", "gdkwayland-sys",
"gdkx11-sys", "gdkx11-sys",
"gtk", "gtk",
"jni", "jni",
"lazy_static",
"libc", "libc",
"log", "log",
"ndk", "ndk",
"ndk-context",
"ndk-sys", "ndk-sys",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
"objc2-foundation", "objc2-foundation",
"objc2-ui-kit",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"percent-encoding",
"raw-window-handle", "raw-window-handle",
"scopeguard",
"tao-macros", "tao-macros",
"unicode-segmentation", "unicode-segmentation",
"url", "url",
@@ -4356,7 +4311,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4373,9 +4328,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.10.2" version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" checksum = "d059f2527558d9dba6f186dec4772610e1aecfd3f94002397613e7e648752b66"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -4424,9 +4379,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.5.5" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" checksum = "be9aa8c59a894f76c29a002501c589de5eb4987a5913d62a6e0a47f320901988"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@@ -4440,29 +4395,28 @@ dependencies = [
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"tauri-winres", "tauri-winres",
"toml 0.9.12+spec-1.1.0",
"walkdir", "walkdir",
] ]
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.5.4" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" checksum = "d3e4e8230d565106aa19dfbaa01a7ed01abf78047fe0577a83377224bd1bf20e"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"brotli", "brotli",
"ico", "ico",
"json-patch", "json-patch",
"plist", "plist",
"png", "png 0.17.16",
"proc-macro2", "proc-macro2",
"quote", "quote",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"syn 2.0.116", "syn 2.0.117",
"tauri-utils", "tauri-utils",
"thiserror 2.0.18", "thiserror 2.0.18",
"time", "time",
@@ -4473,23 +4427,23 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.5.4" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" checksum = "bc8de2cddbbc33dbdf4c84f170121886595efdbcc9cb4b3d76342b79d082cedc"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
] ]
[[package]] [[package]]
name = "tauri-plugin" name = "tauri-plugin"
version = "2.5.3" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" checksum = "f8d5f58bfd0cdcfdbc0a68dc08b354eea2afc551b421de91b07b69e0dd769d57"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"glob", "glob",
@@ -4498,15 +4452,14 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"toml 0.9.12+spec-1.1.0",
"walkdir", "walkdir",
] ]
[[package]] [[package]]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.6.0" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884"
dependencies = [ dependencies = [
"log", "log",
"raw-window-handle", "raw-window-handle",
@@ -4522,13 +4475,15 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.4.5" version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dunce", "dunce",
"glob", "glob",
"log",
"objc2-foundation",
"percent-encoding", "percent-encoding",
"schemars 0.8.22", "schemars 0.8.22",
"serde", "serde",
@@ -4538,7 +4493,7 @@ dependencies = [
"tauri-plugin", "tauri-plugin",
"tauri-utils", "tauri-utils",
"thiserror 2.0.18", "thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
"url", "url",
] ]
@@ -4587,9 +4542,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-store" name = "tauri-plugin-store"
version = "2.4.2" version = "2.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca1a8ff83c269b115e98726ffc13f9e548a10161544a92ad121d6d0a96e16ea" checksum = "6c72dda16786eb4a3f903e43a17b64d8d78dc0f00fe2aa4b757c28f617a8630b"
dependencies = [ dependencies = [
"dunce", "dunce",
"serde", "serde",
@@ -4603,9 +4558,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.10.0" version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" checksum = "1e42bbcb76237351fbaa02f08d808c537dc12eb5a6eabbf3e517b50056334d95"
dependencies = [ dependencies = [
"cookie", "cookie",
"dpi", "dpi",
@@ -4628,9 +4583,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "2.10.0" version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" checksum = "2cadb13dad0c681e1e0a2c49ae488f0e2906ded3d57e7a0017f4aaf46e387117"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@@ -4638,7 +4593,6 @@ dependencies = [
"log", "log",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
"objc2-foundation",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"raw-window-handle", "raw-window-handle",
@@ -4655,24 +4609,24 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.8.2" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" checksum = "55f61d2bf7188fbcf2b0ed095b67a6bc498f713c939314bb19eb700118a573b7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"brotli", "brotli",
"cargo_metadata", "cargo_metadata",
"ctor", "ctor",
"dom_query",
"dunce", "dunce",
"glob", "glob",
"html5ever",
"http", "http",
"infer", "infer",
"json-patch", "json-patch",
"kuchikiki",
"log", "log",
"memchr", "memchr",
"phf 0.11.3", "phf 0.11.3",
"plist",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
@@ -4684,7 +4638,7 @@ dependencies = [
"serde_with", "serde_with",
"swift-rs", "swift-rs",
"thiserror 2.0.18", "thiserror 2.0.18",
"toml 0.9.12+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
"url", "url",
"urlpattern", "urlpattern",
"uuid", "uuid",
@@ -4693,23 +4647,22 @@ dependencies = [
[[package]] [[package]]
name = "tauri-winres" name = "tauri-winres"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6"
dependencies = [ dependencies = [
"dunce", "dunce",
"embed-resource", "embed-resource",
"toml 0.9.12+spec-1.1.0", "toml 1.1.2+spec-1.1.0",
] ]
[[package]] [[package]]
name = "tendril" name = "tendril"
version = "0.4.3" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24"
dependencies = [ dependencies = [
"futf", "new_debug_unreachable",
"mac",
"utf-8", "utf-8",
] ]
@@ -4739,7 +4692,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4750,7 +4703,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4788,9 +4741,9 @@ dependencies = [
[[package]] [[package]]
name = "tinystr" name = "tinystr"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"zerovec", "zerovec",
@@ -4798,9 +4751,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.10.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@@ -4813,9 +4766,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.49.0" version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@@ -4830,13 +4783,13 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -4881,13 +4834,28 @@ version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [ dependencies = [
"indexmap 2.13.0", "indexmap 2.14.0",
"serde_core", "serde_core",
"serde_spanned 1.0.4", "serde_spanned 1.1.1",
"toml_datetime 0.7.5+spec-1.1.0", "toml_datetime 0.7.5+spec-1.1.0",
"toml_parser", "toml_parser",
"toml_writer", "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]] [[package]]
@@ -4908,13 +4876,22 @@ dependencies = [
"serde_core", "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]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.15" version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap 2.13.0", "indexmap 2.14.0",
"toml_datetime 0.6.3", "toml_datetime 0.6.3",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@@ -4925,7 +4902,7 @@ version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [ dependencies = [
"indexmap 2.13.0", "indexmap 2.14.0",
"serde", "serde",
"serde_spanned 0.6.9", "serde_spanned 0.6.9",
"toml_datetime 0.6.3", "toml_datetime 0.6.3",
@@ -4934,30 +4911,30 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
dependencies = [ dependencies = [
"indexmap 2.13.0", "indexmap 2.14.0",
"toml_datetime 0.7.5+spec-1.1.0", "toml_datetime 1.1.1+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow 0.7.14", "winnow 1.0.2",
] ]
[[package]] [[package]]
name = "toml_parser" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [ dependencies = [
"winnow 0.7.14", "winnow 1.0.2",
] ]
[[package]] [[package]]
name = "toml_writer" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]] [[package]]
name = "tower" name = "tower"
@@ -4980,7 +4957,7 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@@ -5024,7 +5001,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5038,9 +5015,9 @@ dependencies = [
[[package]] [[package]]
name = "tray-icon" name = "tray-icon"
version = "0.21.3" version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
@@ -5052,10 +5029,10 @@ dependencies = [
"objc2-core-graphics", "objc2-core-graphics",
"objc2-foundation", "objc2-foundation",
"once_cell", "once_cell",
"png", "png 0.18.1",
"serde", "serde",
"thiserror 2.0.18", "thiserror 2.0.18",
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -5072,9 +5049,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]] [[package]]
name = "unic-char-property" name = "unic-char-property"
@@ -5125,9 +5102,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
@@ -5192,11 +5169,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.21.0" version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
dependencies = [ dependencies = [
"getrandom 0.4.1", "getrandom 0.4.2",
"js-sys", "js-sys",
"serde_core", "serde_core",
"wasm-bindgen", "wasm-bindgen",
@@ -5279,11 +5256,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasip2" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen 0.57.1",
] ]
[[package]] [[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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen 0.51.0",
] ]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.108" version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -5310,23 +5287,19 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.58" version = "0.4.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
dependencies = [ dependencies = [
"cfg-if",
"futures-util",
"js-sys", "js-sys",
"once_cell",
"wasm-bindgen", "wasm-bindgen",
"web-sys",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.108" version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -5334,22 +5307,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.108" version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.108" version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -5371,7 +5344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"indexmap 2.13.0", "indexmap 2.14.0",
"wasm-encoder", "wasm-encoder",
"wasmparser", "wasmparser",
] ]
@@ -5395,22 +5368,34 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.13.0", "indexmap 2.14.0",
"semver", "semver",
] ]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.85" version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "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]] [[package]]
name = "webkit2gtk" name = "webkit2gtk"
version = "2.0.2" version = "2.0.2"
@@ -5477,7 +5462,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5636,7 +5621,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5647,7 +5632,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
@@ -5987,9 +5972,15 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.14" version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"memchr", "memchr",
] ]
@@ -6013,6 +6004,12 @@ dependencies = [
"wit-bindgen-rust-macro", "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]] [[package]]
name = "wit-bindgen-core" name = "wit-bindgen-core"
version = "0.51.0" version = "0.51.0"
@@ -6032,9 +6029,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"heck 0.5.0", "heck 0.5.0",
"indexmap 2.13.0", "indexmap 2.14.0",
"prettyplease", "prettyplease",
"syn 2.0.116", "syn 2.0.117",
"wasm-metadata", "wasm-metadata",
"wit-bindgen-core", "wit-bindgen-core",
"wit-component", "wit-component",
@@ -6050,7 +6047,7 @@ dependencies = [
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"wit-bindgen-core", "wit-bindgen-core",
"wit-bindgen-rust", "wit-bindgen-rust",
] ]
@@ -6062,8 +6059,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.1",
"indexmap 2.13.0", "indexmap 2.14.0",
"log", "log",
"serde", "serde",
"serde_derive", "serde_derive",
@@ -6082,7 +6079,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"id-arena", "id-arena",
"indexmap 2.13.0", "indexmap 2.14.0",
"log", "log",
"semver", "semver",
"serde", "serde",
@@ -6094,30 +6091,29 @@ dependencies = [
[[package]] [[package]]
name = "writeable" name = "writeable"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.54.2" version = "0.55.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" checksum = "3013fd6116aac351dd2e18f349b28b2cfef3a5ff3253a9d0ce2d7193bb1b4429"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"block2", "block2",
"cookie", "cookie",
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
"dom_query",
"dpi", "dpi",
"dunce", "dunce",
"gdkx11", "gdkx11",
"gtk", "gtk",
"html5ever",
"http", "http",
"javascriptcore-rs", "javascriptcore-rs",
"jni", "jni",
"kuchikiki",
"libc", "libc",
"ndk", "ndk",
"objc2", "objc2",
@@ -6175,9 +6171,9 @@ dependencies = [
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.1" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [ dependencies = [
"stable_deref_trait", "stable_deref_trait",
"yoke-derive", "yoke-derive",
@@ -6186,54 +6182,54 @@ dependencies = [
[[package]] [[package]]
name = "yoke-derive" name = "yoke-derive"
version = "0.8.1" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"synstructure", "synstructure",
] ]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.39" version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.39" version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "zerofrom" name = "zerofrom"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
dependencies = [ dependencies = [
"zerofrom-derive", "zerofrom-derive",
] ]
[[package]] [[package]]
name = "zerofrom-derive" name = "zerofrom-derive"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
"synstructure", "synstructure",
] ]
@@ -6254,14 +6250,14 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [ dependencies = [
"displaydoc", "displaydoc",
"yoke", "yoke",
@@ -6270,9 +6266,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.11.5" version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",
@@ -6281,13 +6277,13 @@ dependencies = [
[[package]] [[package]]
name = "zerovec-derive" name = "zerovec-derive"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.116", "syn 2.0.117",
] ]
[[package]] [[package]]
-5
View File
@@ -13,18 +13,14 @@ members = [
[workspace.dependencies] [workspace.dependencies]
base64 = "0.22" base64 = "0.22"
bytes = { version = "1", features = ["serde"] } bytes = { version = "1", features = ["serde"] }
chrono = "0.4"
clap = { version = "4", features = ["derive"] }
eyre = "0.6" eyre = "0.6"
futures = "0.3" futures = "0.3"
gethostname = "1" gethostname = "1"
if-addrs = "0.15" if-addrs = "0.15"
itertools = "0.14"
log = "0.4" log = "0.4"
mdns-sd = "0.18" mdns-sd = "0.18"
mimalloc = { version = "0.1", features = ["secure"] } mimalloc = { version = "0.1", features = ["secure"] }
s2n-quic = { version = "1", features = ["provider-event-tracing"] } s2n-quic = { version = "1", features = ["provider-event-tracing"] }
semver = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
sqlx = { version = "0.8", default-features = false, features = [ sqlx = { version = "0.8", default-features = false, features = [
@@ -40,7 +36,6 @@ tauri-plugin-store = "2"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] } tokio-util = { version = "0.7", features = ["codec"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1", features = ["v7"] } uuid = { version = "1", features = ["v7"] }
walkdir = "2" walkdir = "2"
windows = { version = "0.62", features = [ windows = { version = "0.62", features = [
+4
View File
@@ -19,3 +19,7 @@ eyre = { workspace = true }
sqlx = { workspace = true } sqlx = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[lib]
test = false
doctest = false
+3 -3
View File
@@ -12,10 +12,10 @@ todo = "warn"
unwrap_used = "warn" unwrap_used = "warn"
[dependencies] [dependencies]
# external
bytes = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }
semver = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[lib]
doctest = false
+4 -2
View File
@@ -15,5 +15,7 @@ unwrap_used = "warn"
eyre = { workspace = true } eyre = { workspace = true }
log = { workspace = true } log = { workspace = true }
mdns-sd = { workspace = true } mdns-sd = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true } [lib]
test = false
doctest = false
+3 -3
View File
@@ -9,8 +9,6 @@ todo = "warn"
unwrap_used = "warn" unwrap_used = "warn"
[dependencies] [dependencies]
# local
lanspread-compat = { path = "../lanspread-compat" }
lanspread-db = { path = "../lanspread-db" } lanspread-db = { path = "../lanspread-db" }
lanspread-mdns = { path = "../lanspread-mdns" } lanspread-mdns = { path = "../lanspread-mdns" }
lanspread-proto = { path = "../lanspread-proto" } lanspread-proto = { path = "../lanspread-proto" }
@@ -28,6 +26,8 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-util = { workspace = true } tokio-util = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }
[lib]
doctest = false
+55
View File
@@ -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<PeerEvent>, 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<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
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<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
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<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
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<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
peer_addr: SocketAddr,
) {
send(tx_notify_ui, PeerEvent::PeerLost(peer_addr), "PeerLost");
emit_peer_count(peer_game_db, tx_notify_ui).await;
}
+13 -42
View File
@@ -2,19 +2,24 @@
use std::{net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use lanspread_db::db::{Game, GameFileDescription}; use lanspread_db::db::GameFileDescription;
use tokio::sync::{RwLock, mpsc::UnboundedSender}; use tokio::sync::{RwLock, mpsc::UnboundedSender};
use crate::{ use crate::{
PeerEvent, PeerEvent,
context::Ctx, context::Ctx,
download::download_game_files, download::download_game_files,
events,
identity::FEATURE_LIBRARY_DELTA, identity::FEATURE_LIBRARY_DELTA,
local_games::{ 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}, 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. /// Handles the `ListGames` command.
pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) { pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
log::info!("ListGames command received"); log::info!("ListGames command received");
emit_peer_game_list(&ctx.peer_game_db, tx_notify_ui).await; events::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<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
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<RwLock<PeerGameDB>>,
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
} }
/// Tries to serve a game from local files. /// 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"); log::info!("Got PeerCommand::DownloadGameFiles");
let games_folder = { ctx.game_dir.read().await.clone() }; 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"); log::error!("Cannot handle game file descriptions: games_folder is not set");
return; return;
} };
let games_folder = games_folder.expect("checked above");
// Use majority validation to get trusted file descriptions and peer whitelist // Use majority validation to get trusted file descriptions and peer whitelist
let (validated_descriptions, peer_whitelist, file_peer_map) = { let (validated_descriptions, peer_whitelist, file_peer_map) = {
@@ -312,10 +290,7 @@ pub async fn handle_set_game_dir_command(
/// Handles the `GetPeerCount` command. /// Handles the `GetPeerCount` command.
pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) { pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
log::info!("GetPeerCount command received"); log::info!("GetPeerCount command received");
let peer_count = { ctx.peer_game_db.read().await.get_peer_addresses().len() }; events::emit_peer_count(&ctx.peer_game_db, tx_notify_ui).await;
if let Err(e) = tx_notify_ui.send(PeerEvent::PeerCountUpdated(peer_count)) {
log::error!("Failed to send PeerCountUpdated event: {e}");
}
} }
// ============================================================================= // =============================================================================
@@ -348,11 +323,7 @@ pub async fn update_and_announce_games(
*db_guard = Some(game_db.clone()); *db_guard = Some(game_db.clone());
} }
let all_games = game_db let all_games = game_db.all_games().into_iter().cloned().collect::<Vec<_>>();
.all_games()
.into_iter()
.cloned()
.collect::<Vec<Game>>();
if let Err(e) = tx_notify_ui.send(PeerEvent::LocalGamesUpdated(all_games.clone())) { if let Err(e) = tx_notify_ui.send(PeerEvent::LocalGamesUpdated(all_games.clone())) {
log::error!("Failed to send LocalGamesUpdated event: {e}"); log::error!("Failed to send LocalGamesUpdated event: {e}");
+2
View File
@@ -16,6 +16,7 @@ mod config;
mod context; mod context;
mod download; mod download;
mod error; mod error;
mod events;
mod handlers; mod handlers;
mod identity; mod identity;
mod library; mod library;
@@ -24,6 +25,7 @@ mod network;
mod path_validation; mod path_validation;
mod peer; mod peer;
mod peer_db; mod peer_db;
mod remote_peer;
mod services; mod services;
// ============================================================================= // =============================================================================
+2 -2
View File
@@ -409,8 +409,8 @@ impl PeerGameDB {
#[must_use] #[must_use]
pub fn game_files_for(&self, game_id: &str) -> Vec<(SocketAddr, Vec<GameFileDescription>)> { pub fn game_files_for(&self, game_id: &str) -> Vec<(SocketAddr, Vec<GameFileDescription>)> {
self.peers self.peers
.iter() .values()
.filter_map(|(_, peer)| { .filter_map(|peer| {
peer.files peer.files
.get(game_id) .get(game_id)
.cloned() .cloned()
+59
View File
@@ -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<RwLock<PeerGameDB>>,
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<RwLock<PeerGameDB>>,
peer_addr: SocketAddr,
games: &[Game],
) -> Vec<Game> {
let summaries = games.iter().map(summary_from_game).collect::<Vec<_>>();
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()
}
+18 -1225
View File
@@ -1,1225 +1,18 @@
//! Background services for the peer system. //! Long-running peer services.
//!
use std::{ //! Each submodule owns one runtime concern. The public surface intentionally
collections::{HashMap, HashSet}, //! stays small because `lib.rs` only needs to start these four background tasks.
net::SocketAddr,
path::PathBuf, mod advertise;
sync::Arc, mod discovery;
time::Duration, mod handshake;
}; mod legacy;
mod liveness;
use futures::{SinkExt, StreamExt}; mod local_monitor;
use lanspread_db::db::Game; mod server;
use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, MdnsBrowser, MdnsService}; mod stream;
use lanspread_proto::{
Hello, pub use discovery::run_peer_discovery;
HelloAck, pub use liveness::run_ping_service;
LibraryDelta, pub use local_monitor::run_local_game_monitor;
LibrarySnapshot, pub use server::run_server_component;
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<PeerEvent>,
) -> 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<PeerEvent>,
) -> 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<RwLock<LocalLibraryState>>,
) -> 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<RwLock<LocalLibraryState>>,
remote_rev: u64,
remote_digest: u64,
) -> Option<LibraryUpdate> {
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<RwLock<PeerGameDB>>,
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<String>,
local_library: Arc<RwLock<LocalLibraryState>>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
tx_notify_ui: UnboundedSender<PeerEvent>,
peer_addr: SocketAddr,
peer_id_hint: Option<PeerId>,
) -> 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<PeerId>,
proto_ver: Option<u32>,
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::<u32>().ok());
let library_rev = service
.properties
.get("library_rev")
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(0);
let library_digest = service
.properties
.get("library_digest")
.and_then(|value| value.parse::<u64>().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<PeerEvent>,
) {
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<SocketAddr>,
) -> 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::<Vec<Game>>()
} 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<PeerEvent>, 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<PeerEvent>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
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<PeerEvent>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
downloading_games: Arc<RwLock<HashSet<String>>>,
active_downloads: Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
) {
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<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
let active_ids: Vec<String> = { 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<PeerEvent>, 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}");
}
}
}
}
}
@@ -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<String, String> {
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
}
@@ -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<PeerId>,
proto_ver: Option<u32>,
library_rev: u64,
library_digest: u64,
}
/// Runs the peer discovery service using mDNS.
pub async fn run_peer_discovery(tx_notify_ui: UnboundedSender<PeerEvent>, 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::<u32>().ok()),
library_rev: service
.properties
.get("library_rev")
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(0),
library_digest: service
.properties
.get("library_digest")
.and_then(|value| value.parse::<u64>().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<PeerEvent>,
) {
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<PeerEvent>,
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}");
}
});
}
@@ -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<RwLock<LocalLibraryState>>,
) -> 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<String>,
local_library: Arc<RwLock<LocalLibraryState>>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
tx_notify_ui: UnboundedSender<PeerEvent>,
peer_addr: SocketAddr,
peer_id_hint: Option<PeerId>,
) -> 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<SocketAddr>,
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<String>,
local_library: Arc<RwLock<LocalLibraryState>>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
tx_notify_ui: UnboundedSender<PeerEvent>,
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<RwLock<PeerGameDB>>,
peer_id: PeerId,
peer_addr: SocketAddr,
library_rev: u64,
library_digest: u64,
features: Vec<String>,
) -> 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<RwLock<LocalLibraryState>>,
peer_game_db: &Arc<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
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<RwLock<LocalLibraryState>>,
) {
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<RwLock<LocalLibraryState>>,
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<RwLock<LocalLibraryState>>,
remote_rev: u64,
remote_digest: u64,
) -> Option<LibraryUpdate> {
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,
)))
}
@@ -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<PeerEvent>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
) -> 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(());
}
}
@@ -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<PeerEvent>,
peer_game_db: Arc<RwLock<PeerGameDB>>,
downloading_games: Arc<RwLock<HashSet<String>>>,
active_downloads: Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
) {
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<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
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<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
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<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
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<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
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<RwLock<PeerGameDB>>,
downloading_games: &Arc<RwLock<HashSet<String>>>,
active_downloads: &Arc<RwLock<HashMap<String, JoinHandle<()>>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
let active_ids = {
downloading_games
.read()
.await
.iter()
.cloned()
.collect::<Vec<_>>()
};
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<RwLock<PeerGameDB>>, game_id: &str) -> bool {
let guard = peer_game_db.read().await;
!guard.peers_with_game(game_id).is_empty()
}
@@ -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<PeerEvent>, 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}");
}
}
}
}
}
@@ -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<PeerEvent>,
) -> 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<PeerEvent>,
) -> 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(())
}
@@ -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<SendStream, LengthDelimitedCodec>;
/// Handles a bidirectional stream from a peer.
pub(super) async fn handle_peer_stream(
stream: BidirectionalStream,
ctx: PeerCtx,
remote_addr: Option<SocketAddr>,
) -> 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<SocketAddr>,
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<SocketAddr>) {
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::<Vec<Game>>()
} 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<SocketAddr>,
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<SocketAddr>,
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<SocketAddr>, 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<u8>,
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<SocketAddr>, 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<SocketAddr>, games: Vec<Game>) {
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",
);
}
}
+4
View File
@@ -20,3 +20,7 @@ bytes = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[lib]
test = false
doctest = false
@@ -13,6 +13,8 @@ edition = "2024"
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "lanspread_tauri_deno_ts_lib" name = "lanspread_tauri_deno_ts_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
test = false
doctest = false
[lints.clippy] [lints.clippy]
pedantic = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 }
@@ -27,7 +29,6 @@ tauri-build = { version = "2", features = [] }
# local # local
lanspread-peer = { path = "../../lanspread-peer" } lanspread-peer = { path = "../../lanspread-peer" }
lanspread-db = { path = "../../lanspread-db" } lanspread-db = { path = "../../lanspread-db" }
lanspread-mdns = { path = "../../lanspread-mdns" }
lanspread-compat = { path = "../../lanspread-compat" } lanspread-compat = { path = "../../lanspread-compat" }
# external # external
@@ -35,8 +36,6 @@ base64 = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }
log = { workspace = true } log = { workspace = true }
mimalloc = { workspace = true } mimalloc = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
tauri-plugin-log = { workspace = true } tauri-plugin-log = { workspace = true }
tauri-plugin-shell = { workspace = true } tauri-plugin-shell = { workspace = true }
@@ -895,7 +895,7 @@ pub fn run() {
log::info!("Peer system initialized successfully with games directory"); log::info!("Peer system initialized successfully with games directory");
// Wait a moment for local game database to be loaded before starting discovery // 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 // Start peer discovery and request games from other peers
if let Err(e) = request_games(state).await { if let Err(e) = request_games(state).await {
+3 -1
View File
@@ -11,4 +11,6 @@ pedantic = { level = "warn", priority = -1 }
todo = "warn" todo = "warn"
unwrap_used = "warn" unwrap_used = "warn"
[dependencies] [lib]
test = false
doctest = false