diff --git a/Cargo.lock b/Cargo.lock index 3ffdd7e7d..cb8e3d3e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "brotli", "flate2", @@ -238,7 +238,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -371,9 +371,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -389,9 +389,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -448,14 +448,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -522,9 +522,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -586,7 +586,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2", + "socket2 0.5.10", "windows-sys 0.59.0", ] @@ -627,7 +627,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -638,7 +638,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -682,7 +682,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -692,7 +692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -725,7 +725,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -769,7 +769,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -803,12 +803,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1004,7 +1004,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1077,7 +1077,7 @@ checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "uuid", ] @@ -1540,7 +1540,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1568,9 +1568,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -1728,9 +1728,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64 0.22.1", "bytes", @@ -1744,7 +1744,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -1915,14 +1915,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "699c1b6d335e63d0ba5c1e1c7f647371ce989c3bcbe1f7ed2b85fa56e3bd1a21" dependencies = [ "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -1957,6 +1957,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2044,7 +2055,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2126,9 +2137,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", @@ -2292,12 +2303,11 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" [[package]] name = "notify" -version = "8.0.0" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97" dependencies = [ "bitflags 2.9.1", - "filetime", "fsevent-sys", "inotify", "kqueue", @@ -2306,7 +2316,7 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2412,7 +2422,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2423,9 +2433,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.0+3.5.0" +version = "300.5.1+3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" dependencies = [ "cc", ] @@ -2531,7 +2541,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2561,7 +2571,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2650,12 +2660,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2738,7 +2748,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2778,7 +2788,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -2815,7 +2825,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -2942,9 +2952,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.20" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", @@ -3082,22 +3092,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "once_cell", "ring", @@ -3119,9 +3129,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -3235,7 +3245,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3369,6 +3379,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spinning_top" version = "0.3.0" @@ -3408,9 +3428,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -3434,7 +3454,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3497,7 +3517,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3508,7 +3528,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3571,17 +3591,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", - "socket2", + "slab", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.52.0", ] @@ -3594,7 +3616,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3753,7 +3775,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3828,7 +3850,7 @@ dependencies = [ "serde_json", "serde_yaml", "shared", - "socket2", + "socket2 0.6.0", "strsim", "tempfile", "tokio", @@ -4036,7 +4058,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -4071,7 +4093,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4106,7 +4128,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4155,9 +4177,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -4214,7 +4236,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4225,7 +4247,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4236,9 +4258,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result", @@ -4290,6 +4312,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4314,13 +4345,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4333,6 +4380,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4345,6 +4398,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4357,12 +4416,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4375,6 +4446,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4387,6 +4464,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4399,6 +4482,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4411,6 +4500,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.5.40" @@ -4507,7 +4602,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4537,7 +4632,7 @@ checksum = "42bfd190a07ca8cfde7cd4c52b3ac463803dc07323db8c34daa697e86365978c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4560,7 +4655,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -4581,7 +4676,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4601,7 +4696,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -4641,7 +4736,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 6a972cc50..398c3dca9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -57,7 +57,7 @@ arc-swap = "1" dashmap = "6" hyper = "1" hyper-util = "0" -socket2 = "0" +socket2 = { version = "0", features = ["all"] } #[cfg(target_os = "macos")] libc = "0" #[cfg(target_os = "windows")] diff --git a/backend/src/api/api_utils.rs b/backend/src/api/api_utils.rs index 370a49067..8f36e6679 100644 --- a/backend/src/api/api_utils.rs +++ b/backend/src/api/api_utils.rs @@ -296,14 +296,14 @@ async fn resolve_streaming_strategy(app_state: &AppState, stream_url: &str, inpu }; let stream_response_params = match &*provider_connection_guard { ProviderAllocation::Exhausted => { - debug!("Input {} is exhausted. No connections allowed.", input.name); + debug!("Input {} is exhausted. No connections allowed.", input.name); let stream = create_provider_connections_exhausted_stream(&app_state.app_config, &[]); ProviderStreamState::Custom(stream) } ProviderAllocation::Available(ref provider) | ProviderAllocation::GracePeriod(ref provider) => { // force_stream_provider means we keep the url and the provider. - // If force_stream_provider or the input is the same as the config we dont need to get new url + // If force_stream_provider or the input is the same as the config we don't need to get new url let (provider, url) = if force_provider.is_some() || provider.id == input.id { (input.name.to_string(), stream_url.to_string()) } else { @@ -348,7 +348,7 @@ async fn create_stream_response_details(app_state: &AppState, .and_then(|r| r.stream.as_ref()).map_or_else(default_grace_period_millis, |s| s.grace_period_millis); let grace_period_millis = get_grace_period_millis(connection_permission, &streaming_strategy.provider_stream_state, config_grace_period_millis); match streaming_strategy.provider_stream_state { - // custom stream means we display our own stream like connection exhausted, channel unavailable... + // custom stream means we display our own stream like connection exhausted, channel-unavailable... ProviderStreamState::Custom(provider_stream) => { let (stream, stream_info) = provider_stream; StreamDetails { @@ -377,7 +377,7 @@ async fn create_stream_response_details(app_state: &AppState, ((None, None), None) }; - // if we have no stream we should release the provider + // if we have no stream, we should release the provider if stream.is_none() { if let Some(guard) = streaming_strategy.provider_connection_guard.take() { drop(guard); @@ -433,7 +433,7 @@ where || extract_extension_from_url(url).map_or_else(String::new, std::string::ToString::to_string), std::string::ToString::to_string); - // if there is a action_path (like for timeshift duration/start) it will be added in front of the stream_id + // if there is an action_path (like for timeshift duration/start), it will be added in front of the stream_id if self.action_path.is_empty() { format!("{provider_id}{extension}") } else { @@ -468,7 +468,7 @@ where if redirect_request { - // handle redirect for series but why ? + // handle redirect for series but why? if params.cluster == XtreamCluster::Series { let ext = params.stream_ext.unwrap_or_default(); let url = params.input.url.as_str(); @@ -525,13 +525,13 @@ fn prepare_body_stream(app_state: &AppState, item_type: PlaylistItemType, stream } /// # Panics -pub async fn force_provider_stream_response(app_state: &AppState, +pub async fn force_provider_stream_response(addr: &str, + app_state: &AppState, user_session: &UserSession, item_type: PlaylistItemType, req_headers: &HeaderMap, input: &ConfigInput, - user: &ProxyUserCredentials, - addr: &str) -> impl IntoResponse + Send { + user: &ProxyUserCredentials) -> impl IntoResponse + Send { let stream_options = get_stream_options(app_state); let share_stream = false; let connection_permission = UserConnectionPermission::Allowed; @@ -541,6 +541,7 @@ pub async fn force_provider_stream_response(app_state: &AppState, if stream_details.has_stream() { let provider_response = stream_details.stream_info.as_ref().map(|(h, sc, url)| (h.clone(), *sc, url.clone())); + app_state.active_users.update_session_addr(&user.username, &user_session.token, addr).await; let stream = ActiveClientStream::new(stream_details, app_state, user, connection_permission, addr).await; let (status_code, header_map) = get_stream_response_with_headers(provider_response.map(|(h, s, _)| (h, s))); @@ -566,17 +567,11 @@ pub async fn force_provider_stream_response(app_state: &AppState, /// # Panics #[allow(clippy::too_many_arguments)] -pub async fn stream_response(app_state: &AppState, - session_token: &str, - virtual_id: u32, - item_type: PlaylistItemType, - stream_url: &str, - req_headers: &HeaderMap, - input: &ConfigInput, - target: &ConfigTarget, - user: &ProxyUserCredentials, - connection_permission: UserConnectionPermission, - addr: &str) -> impl IntoResponse + Send { +pub async fn stream_response(addr: &str, + app_state: &AppState, session_token: &str, virtual_id: u32, + item_type: PlaylistItemType, stream_url: &str, req_headers: &HeaderMap, + input: &ConfigInput, target: &ConfigTarget, user: &ProxyUserCredentials, + connection_permission: UserConnectionPermission) -> impl IntoResponse + Send { if log_enabled!(log::Level::Trace) { trace!("Try to open stream {}", sanitize_sensitive_info(stream_url)); } if connection_permission == UserConnectionPermission::Exhausted { @@ -632,7 +627,7 @@ pub async fn stream_response(app_state: &AppState, if let Some(provider) = provider_name { if matches!(item_type, PlaylistItemType::LiveHls | PlaylistItemType::LiveDash | PlaylistItemType::Video | PlaylistItemType::Series | PlaylistItemType::Catchup) { - let _ = app_state.active_users.create_user_session(user, session_token, virtual_id, &provider, &session_url, connection_permission).await; + let _ = app_state.active_users.create_user_session(user, session_token, virtual_id, &provider, &session_url, addr, connection_permission).await; } } diff --git a/backend/src/api/config_watch.rs b/backend/src/api/config_watch.rs index 02cbe919c..dcf6b7112 100644 --- a/backend/src/api/config_watch.rs +++ b/backend/src/api/config_watch.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc}; use tokio_util::sync::CancellationToken; -use shared::model::ConfigPaths; +use shared::model::{ConfigPaths}; enum ConfigFile { Config, @@ -73,7 +73,8 @@ impl ConfigFile { async fn load_sources(app_state: &Arc) -> Result<(), TuliproxError> { let paths = app_state.app_config.paths.load(); let sources_file = paths.sources_file_path.as_str(); - let sources_dto = read_sources_file(sources_file, true, true)?; + let config = &app_state.app_config.config.load(); + let sources_dto = read_sources_file(sources_file, true, true, config.get_hdhr_device_overview().as_ref())?; let sources: SourcesConfig = SourcesConfig::try_from(sources_dto)?; info!("Loaded sources file {sources_file}"); update_app_state_sources(app_state, sources).await diff --git a/backend/src/api/endpoints/hls_api.rs b/backend/src/api/endpoints/hls_api.rs index 6a8cd00d1..70deb9c28 100644 --- a/backend/src/api/endpoints/hls_api.rs +++ b/backend/src/api/endpoints/hls_api.rs @@ -35,7 +35,7 @@ fn hls_response(hls_content: String) -> impl IntoResponse + Send { #[allow(clippy::too_many_arguments)] pub(in crate::api) async fn handle_hls_stream_request( - fingerprint: &str, + fingerprint: &str, addr: &str, app_state: &Arc, user: &ProxyUserCredentials, user_session: Option<&UserSession>, @@ -61,7 +61,7 @@ pub(in crate::api) async fn handle_hls_stream_request( Some(provider_cfg) => { let stream_url = get_stream_alternative_url(&url, input, &provider_cfg); let user_session_token = format!("{fingerprint}{virtual_id}"); - let session_token= app_state.active_users.create_user_session(user, &user_session_token, virtual_id, &provider_cfg.name, &stream_url, connection_permission).await; + let session_token= app_state.active_users.create_user_session(user, &user_session_token, virtual_id, &provider_cfg.name, &stream_url, addr, connection_permission).await; (stream_url, session_token) }, None => (url, None), @@ -128,7 +128,7 @@ async fn hls_api_stream( if session.virtual_id == virtual_id { if is_seek_request(XtreamCluster::Live, &req_headers).await { // partial request means we are in reverse proxy mode, seek happened - return force_provider_stream_response(&app_state, session, PlaylistItemType::LiveHls, &req_headers, &input, &user, &addr).await.into_response() + return force_provider_stream_response(&addr, &app_state, session, PlaylistItemType::LiveHls, &req_headers, &input, &user).await.into_response() } } else { return axum::http::StatusCode::BAD_REQUEST.into_response(); @@ -140,10 +140,10 @@ async fn hls_api_stream( } if is_hls_url(&session.stream_url) { - return handle_hls_stream_request(&fingerprint, &app_state, &user, Some(session), &session.stream_url, virtual_id, &input, connection_permission).await.into_response(); + return handle_hls_stream_request(&fingerprint, &addr, &app_state, &user, Some(session), &session.stream_url, virtual_id, &input, connection_permission).await.into_response(); } - force_provider_stream_response(&app_state, session, PlaylistItemType::LiveHls, &req_headers, &input, &user, &addr).await.into_response() + force_provider_stream_response(&addr, &app_state, session, PlaylistItemType::LiveHls, &req_headers, &input, &user).await.into_response() } else { axum::http::StatusCode::BAD_REQUEST.into_response() } diff --git a/backend/src/api/endpoints/m3u_api.rs b/backend/src/api/endpoints/m3u_api.rs index 384b21269..756fdfc19 100644 --- a/backend/src/api/endpoints/m3u_api.rs +++ b/backend/src/api/endpoints/m3u_api.rs @@ -98,7 +98,7 @@ async fn m3u_api_stream( } if session.virtual_id == virtual_id && is_seek_request(cluster, req_headers).await { // partial request means we are in reverse proxy mode, seek happened - return force_provider_stream_response(app_state, session, pli.item_type, req_headers, &input, &user, addr).await.into_response(); + return force_provider_stream_response(addr, app_state, session, pli.item_type, req_headers, &input, &user).await.into_response(); } session.stream_url.as_str() } else { @@ -135,10 +135,10 @@ async fn m3u_api_stream( let is_hls_request = pli.item_type == PlaylistItemType::LiveHls || pli.item_type == PlaylistItemType::LiveDash || extension == HLS_EXT; // Reverse proxy mode if is_hls_request { - return handle_hls_stream_request(fingerprint, app_state, &user, user_session.as_ref(), &pli.url, pli.virtual_id, &input, connection_permission).await.into_response(); + return handle_hls_stream_request(fingerprint, addr, app_state, &user, user_session.as_ref(), &pli.url, pli.virtual_id, &input, connection_permission).await.into_response(); } - stream_response(app_state, &session_key, pli.virtual_id, pli.item_type, session_url, req_headers, &input, &target, &user, connection_permission, addr).await.into_response() + stream_response(addr, app_state, &session_key, pli.virtual_id, pli.item_type, session_url, req_headers, &input, &target, &user, connection_permission).await.into_response() } async fn m3u_api_resource( diff --git a/backend/src/api/endpoints/xtream_api.rs b/backend/src/api/endpoints/xtream_api.rs index 4747fad9a..c644df7fd 100644 --- a/backend/src/api/endpoints/xtream_api.rs +++ b/backend/src/api/endpoints/xtream_api.rs @@ -214,7 +214,7 @@ async fn xtream_player_api_stream( if session.virtual_id == virtual_id && is_seek_request(cluster, req_headers).await { // partial request means we are in reverse proxy mode, seek happened - return force_provider_stream_response(app_state, session, item_type, req_headers, &input, &user, addr).await.into_response(); + return force_provider_stream_response(addr, app_state, session, item_type, req_headers, &input, &user).await.into_response(); } session.stream_url.as_str() @@ -260,10 +260,10 @@ async fn xtream_player_api_stream( let is_hls_request = item_type == PlaylistItemType::LiveHls || item_type == PlaylistItemType::LiveDash || extension == HLS_EXT; // Reverse proxy mode if is_hls_request { - return handle_hls_stream_request(fingerprint, app_state, &user, user_session.as_ref(), &stream_url, pli.virtual_id, &input, connection_permission).await.into_response(); + return handle_hls_stream_request(fingerprint, addr, app_state, &user, user_session.as_ref(), &stream_url, pli.virtual_id, &input, connection_permission).await.into_response(); } - stream_response(app_state, session_key.as_str(), pli.virtual_id, item_type, &stream_url, req_headers, &input, &target, &user, connection_permission, addr).await.into_response() + stream_response(addr, app_state, session_key.as_str(), pli.virtual_id, item_type, &stream_url, req_headers, &input, &target, &user, connection_permission).await.into_response() } // Used by webui @@ -312,7 +312,7 @@ async fn xtream_player_api_stream_with_token( // Reverse proxy mode if is_hls_request { - return handle_hls_stream_request(fingerprint, app_state, &user, None, &pli.url, pli.virtual_id, &input, UserConnectionPermission::Allowed).await.into_response(); + return handle_hls_stream_request(fingerprint, addr, app_state, &user, None, &pli.url, pli.virtual_id, &input, UserConnectionPermission::Allowed).await.into_response(); } let extension = stream_ext.unwrap_or_else( @@ -330,7 +330,7 @@ async fn xtream_player_api_stream_with_token( stream_req.context)); trace_if_enabled!("Streaming stream request from {}", sanitize_sensitive_info(&stream_url)); - stream_response(app_state, session_key.as_str(), pli.virtual_id, pli.item_type, &stream_url, req_headers, &input, &target, &user, UserConnectionPermission::Allowed, addr).await.into_response() + stream_response(addr, app_state, session_key.as_str(), pli.virtual_id, pli.item_type, &stream_url, req_headers, &input, &target, &user, UserConnectionPermission::Allowed).await.into_response() } else { axum::http::StatusCode::BAD_REQUEST.into_response() } diff --git a/backend/src/api/model/active_user_manager.rs b/backend/src/api/model/active_user_manager.rs index dbb8b7d9e..fa6999774 100644 --- a/backend/src/api/model/active_user_manager.rs +++ b/backend/src/api/model/active_user_manager.rs @@ -76,7 +76,6 @@ impl ConnectionGuardUserManager { fn is_log_user_enabled(&self)-> bool { self.log_active_user } - } pub struct UserConnectionGuard { @@ -100,6 +99,7 @@ pub struct UserSession { pub virtual_id: u32, pub provider: String, pub stream_url: String, + pub addr: String, pub ts: u64, pub permission: UserConnectionPermission, } @@ -142,13 +142,14 @@ pub struct ActiveUserManager { user: Arc>>, user_by_addr: Arc>>, gc_ts: Option, + close_signal_tx: tokio::sync::broadcast::Sender, } impl ActiveUserManager { pub fn new(config: &Config) -> Self { let log_active_user = config.log.as_ref().is_some_and(|l| l.log_active_user); let (grace_period_millis, grace_period_timeout_secs) = get_grace_options(config); - + let (close_signal_tx, _) = tokio::sync::broadcast::channel(10); Self { grace_period_millis : AtomicU64::new(grace_period_millis), grace_period_timeout_secs: AtomicU64::new(grace_period_timeout_secs), @@ -156,6 +157,7 @@ impl ActiveUserManager { user: Arc::new(RwLock::new(HashMap::new())), user_by_addr: Arc::new(RwLock::new(HashMap::new())), gc_ts: Some(AtomicU64::new(current_time_secs())), + close_signal_tx } } @@ -271,19 +273,23 @@ impl ActiveUserManager { sessions.iter().find(|&session| session.token.eq(token)) } - fn new_user_session(session_token: &str, virtual_id: u32, provider: &str, stream_url: &str, connection_permission: UserConnectionPermission) -> UserSession { + fn new_user_session(session_token: &str, virtual_id: u32, provider: &str, stream_url: &str, addr: &str, + connection_permission: UserConnectionPermission) -> UserSession { UserSession { token: session_token.to_string(), virtual_id, provider: provider.to_string(), stream_url: stream_url.to_string(), + addr: addr.to_string(), ts: current_time_secs(), permission: connection_permission, } } + #[allow(clippy::too_many_arguments)] pub async fn create_user_session(&self, user: &ProxyUserCredentials, session_token: &str, virtual_id: u32, - provider: &str, stream_url: &str, connection_permission: UserConnectionPermission) -> Option { + provider: &str, stream_url: &str, addr: &str, + connection_permission: UserConnectionPermission) -> Option { self.gc().await; let mut lock = self.user.write().await; if let Some(connection_data) = lock.get_mut(&user.username) { @@ -305,14 +311,14 @@ impl ActiveUserManager { // no session creates new one debug!("Creating session for user {} with token {session_token} {}", user.username, sanitize_sensitive_info(stream_url)); - let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, connection_permission); + let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, addr, connection_permission); let token = session.token.clone(); connection_data.add_session(session); Some(token) } else { debug!("Creating session for user {} with token {session_token} {}", user.username, sanitize_sensitive_info(stream_url)); let mut connection_data = UserConnectionData::new(0, user.max_connections); - let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, connection_permission); + let session = Self::new_user_session(session_token, virtual_id, provider, stream_url, addr, connection_permission); let token = session.token.clone(); connection_data.add_session(session); lock.insert(user.username.to_string(), connection_data); @@ -320,6 +326,26 @@ impl ActiveUserManager { } } + pub async fn update_session_addr(&self, username: &str, token: &str, addr: &str) { + let mut lock = self.user.write().await; + if let Some(connection_data) = lock.get_mut(username) { + for session in &mut connection_data.sessions { + if session.token.eq(token) { + self.drop_connection(&session.addr); + session.addr = addr.to_string(); + } + } + } + } + + fn drop_connection(&self, addr: &str) { + let _ = self.close_signal_tx.send(addr.to_string()); + } + + pub fn get_close_connection_channel(&self) -> tokio::sync::broadcast::Receiver { + self.close_signal_tx.subscribe() + } + pub async fn get_user_session(&self, username: &str, token: &str) -> Option { self.update_user_session(username, token).await } diff --git a/backend/src/api/model/model_utils.rs b/backend/src/api/model/model_utils.rs index f865e496a..df5060064 100644 --- a/backend/src/api/model/model_utils.rs +++ b/backend/src/api/model/model_utils.rs @@ -29,7 +29,7 @@ pub fn get_stream_response_with_headers(custom: Option<(Vec<(String, String)>, S let default_headers = vec![ ("content-type", "application/octet-stream"), - ("connection", "keep-alive"), + //("connection", "keep-alive"), ]; for (key, value) in default_headers { diff --git a/backend/src/api/serve.rs b/backend/src/api/serve.rs index 37f6ddbd2..8bcafc6b7 100644 --- a/backend/src/api/serve.rs +++ b/backend/src/api/serve.rs @@ -6,7 +6,7 @@ use hyper::body::Incoming; use hyper_util::rt::{TokioExecutor, TokioIo}; use hyper_util::server::conn::auto::Builder; use hyper_util::service::TowerToHyperService; -use log::{error, info, trace}; +use log::{debug, error, trace}; use socket2::{SockRef, TcpKeepalive}; use std::convert::Infallible; use std::fmt::Debug; @@ -43,7 +43,6 @@ pub async fn serve(listener: tokio::net::TcpListener, cancel_token: Option, user_manager: Arc) { let (signal_tx, _signal_rx) = watch::channel(()); - let (_close_tx, close_rx) = watch::channel(()); let mut make_service = router.into_make_service_with_connect_info::(); match cancel_token { @@ -55,7 +54,7 @@ pub async fn serve(listener: tokio::net::TcpListener, } accept_result = listener.accept() => { let Ok((socket, remote_addr)) = accept_result else { continue }; - handle_connection(&mut make_service, &signal_tx, &close_rx, socket, remote_addr, Arc::clone(&user_manager)).await; + handle_connection(&mut make_service, &signal_tx, socket, remote_addr, Arc::clone(&user_manager)).await; } } } @@ -63,7 +62,7 @@ pub async fn serve(listener: tokio::net::TcpListener, None => { loop { let Ok((socket, remote_addr)) = listener.accept().await else { continue }; - handle_connection(&mut make_service, &signal_tx, &close_rx, socket, remote_addr, Arc::clone(&user_manager)).await; + handle_connection(&mut make_service, &signal_tx, socket, remote_addr, Arc::clone(&user_manager)).await; } } } @@ -73,7 +72,6 @@ pub async fn serve(listener: tokio::net::TcpListener, async fn handle_connection( make_service: &mut M, signal_tx: &watch::Sender<()>, - close_rx: &watch::Receiver<()>, socket: tokio::net::TcpStream, remote_addr: SocketAddr, user_manager: Arc, @@ -127,7 +125,7 @@ where let hyper_service = TowerToHyperService::new(tower_service); let signal_tx = signal_tx.clone(); - let close_rx = close_rx.clone(); + let addr_str = remote_addr.to_string(); tokio::spawn(async move { #[allow(unused_mut)] @@ -136,8 +134,9 @@ where let mut signal_closed = pin!(signal_tx.closed().fuse()); let user_manager_clone = Arc::clone(&user_manager); + let mut addr_close_rx = user_manager_clone.get_close_connection_channel(); let connection_closed = async move || { - info!("Connection closed: {remote_addr}"); + debug!("Connection closed: {remote_addr}"); let addr = remote_addr.to_string(); user_manager_clone.remove_connection(&addr).await; }; @@ -156,9 +155,14 @@ where trace!("signal received in task, starting graceful shutdown"); conn.as_mut().graceful_shutdown(); } + Ok(msg) = addr_close_rx.recv() => { + if msg == addr_str { + debug!("Forced client disconnect {msg}"); + conn.as_mut().graceful_shutdown(); + break; + } + } } } - - drop(close_rx); }); } diff --git a/backend/src/model/config/base.rs b/backend/src/model/config/base.rs index 485c608a4..8f9d506f2 100644 --- a/backend/src/model/config/base.rs +++ b/backend/src/model/config/base.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use log::{error, info}; use path_clean::PathClean; use shared::error::{TuliproxError}; -use shared::model::ConfigDto; +use shared::model::{ConfigDto, HdHomeRunDeviceOverview}; use shared::utils::set_sanitize_sensitive_info; use crate::model::{macros, ConfigApi, ReverseProxyConfig, ScheduleConfig}; use crate::model::{HdHomeRunConfig, IpCheckConfig, LogConfig, MessagingConfig, ProxyConfig, VideoConfig, WebUiConfig}; @@ -101,6 +101,14 @@ impl Config { create_directories(self, &temp_path); let _ = tempfile::env::override_temp_dir(&temp_path); } + + pub fn get_hdhr_device_overview(&self) -> Option { + self.hdhomerun.as_ref().map(|hdhr| + HdHomeRunDeviceOverview { + enabled: hdhr.enabled, + devices: hdhr.devices.iter().map(|d| d.name.to_string()).collect::>(), + }) + } } macros::from_impl!(Config); diff --git a/backend/src/model/config/hdhomerun.rs b/backend/src/model/config/hdhomerun.rs index d23a50e67..7ea3a0509 100644 --- a/backend/src/model/config/hdhomerun.rs +++ b/backend/src/model/config/hdhomerun.rs @@ -56,4 +56,4 @@ impl From<&HdHomeRunConfigDto> for HdHomeRunConfig { devices: dto.devices.iter().map(Into::into).collect(), } } -} \ No newline at end of file +} diff --git a/backend/src/utils/file/config_reader.rs b/backend/src/utils/file/config_reader.rs index 0bf109974..4537348d1 100644 --- a/backend/src/utils/file/config_reader.rs +++ b/backend/src/utils/file/config_reader.rs @@ -9,7 +9,7 @@ use chrono::Local; use log::{error, info, warn}; use serde::Serialize; use shared::error::{create_tuliprox_error, info_err, to_io_error, TuliproxError, TuliproxErrorKind}; -use shared::model::{ApiProxyConfigDto, AppConfigDto, ConfigDto, ConfigPaths, SourcesConfigDto}; +use shared::model::{ApiProxyConfigDto, AppConfigDto, ConfigDto, ConfigPaths, HdHomeRunDeviceOverview, SourcesConfigDto}; use shared::utils::{CONSTANTS}; use std::env; use std::fs::File; @@ -61,14 +61,14 @@ pub fn read_api_proxy_config(config: &AppConfig, resolve_env: bool) -> Result Result { +pub fn read_sources_file(sources_file: &str, resolve_env: bool, include_computed: bool, hdhr_config: Option<&HdHomeRunDeviceOverview>) -> Result { match open_file(&std::path::PathBuf::from(sources_file)) { Ok(file) => { let maybe_sources: Result = serde_yaml::from_reader(config_file_reader(file, resolve_env)); match maybe_sources { Ok(mut sources) => { if resolve_env { - if let Err(err) = sources.prepare(include_computed) { + if let Err(err) = sources.prepare(include_computed, hdhr_config) { return Err(info_err!(format!("Can't read the sources-config file: {sources_file}: {err}"))); } } @@ -106,8 +106,8 @@ pub fn read_app_config_dto(paths: &ConfigPaths, let sources_file = paths.sources_file_path.as_str(); let api_proxy_file = paths.api_proxy_file_path.as_str(); - let sources = read_sources_file(sources_file, resolve_env, include_computed)?; let config = read_config_file(config_file, resolve_env)?; + let sources = read_sources_file(sources_file, resolve_env, include_computed, config.get_hdhr_device_overview().as_ref())?; let mappings = if let Some(mappings_file) = paths.mapping_file_path.as_ref() { read_mappings_file(mappings_file, resolve_env).unwrap_or(None) } else { @@ -133,9 +133,9 @@ pub fn read_initial_app_config(paths: &mut ConfigPaths, let config_file = paths.config_file_path.as_str(); let sources_file = paths.sources_file_path.as_str(); - let sources_dto = read_sources_file(sources_file, resolve_env, include_computed)?; - let sources: SourcesConfig = SourcesConfig::try_from(sources_dto)?; let config_dto = read_config_file(config_file, resolve_env)?; + let sources_dto = read_sources_file(sources_file, resolve_env, include_computed, config_dto.get_hdhr_device_overview().as_ref())?; + let sources: SourcesConfig = SourcesConfig::try_from(sources_dto)?; let mut config: Config = Config::from(config_dto); config.prepare(config_path)?; config.update_runtime(); diff --git a/shared/src/model/config/base.rs b/shared/src/model/config/base.rs index e0e9364ff..f59e54b36 100644 --- a/shared/src/model/config/base.rs +++ b/shared/src/model/config/base.rs @@ -1,6 +1,8 @@ use crate::error::{TuliproxError, TuliproxErrorKind}; -use crate::model::{WebUiConfigDto, MessagingConfigDto, IpCheckConfigDto, HdHomeRunConfigDto, VideoConfigDto, ScheduleConfigDto, LogConfigDto, ReverseProxyConfigDto, ProxyConfigDto, ConfigApiDto}; -use crate::utils::{default_connect_timeout_secs}; +use crate::model::{ConfigApiDto, HdHomeRunConfigDto, IpCheckConfigDto, LogConfigDto, + MessagingConfigDto, ProxyConfigDto, ReverseProxyConfigDto, ScheduleConfigDto, + VideoConfigDto, WebUiConfigDto}; +use crate::utils::default_connect_timeout_secs; pub const DEFAULT_USER_AGENT: &str = "VLC/3.0.16 LibVLC/3.0.16"; @@ -50,8 +52,12 @@ pub struct ConfigDto { pub ipcheck: Option, } -impl ConfigDto { +pub struct HdHomeRunDeviceOverview { + pub enabled: bool, + pub devices: Vec, +} +impl ConfigDto { pub fn prepare(&mut self) -> Result<(), TuliproxError> { if let Some(mins) = self.sleep_timer_mins { if mins == 0 { @@ -85,8 +91,7 @@ impl ConfigDto { } fn prepare_hdhomerun(&mut self) -> Result<(), TuliproxError> { - if let Some(old_hdhomerun) = &self.hdhomerun { - let mut hdhomerun = (*old_hdhomerun).clone(); + if let Some(hdhomerun) = &mut self.hdhomerun { if hdhomerun.enabled { hdhomerun.prepare(self.api.port)?; } @@ -132,4 +137,12 @@ impl ConfigDto { } true } + + pub fn get_hdhr_device_overview(&self) -> Option { + self.hdhomerun.as_ref().map(|hdhr| + HdHomeRunDeviceOverview { + enabled: hdhr.enabled, + devices: hdhr.devices.iter().map(|d| d.name.to_string()).collect::>(), + }) + } } \ No newline at end of file diff --git a/shared/src/model/config/source.rs b/shared/src/model/config/source.rs index 98d955a1f..fe061e3fd 100644 --- a/shared/src/model/config/source.rs +++ b/shared/src/model/config/source.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use crate::create_tuliprox_error_result; use crate::error::{handle_tuliprox_error_result_list, TuliproxError, TuliproxErrorKind}; use crate::foundation::filter::prepare_templates; -use crate::model::{ConfigInputDto, PatternTemplate}; +use crate::model::{ConfigInputDto, HdHomeRunDeviceOverview, PatternTemplate}; use crate::model::config::target::ConfigTargetDto; use crate::utils::default_as_default; @@ -40,14 +40,14 @@ pub struct SourcesConfigDto { } impl SourcesConfigDto { - pub fn prepare(&mut self, include_computed: bool) -> Result<(), TuliproxError> { + pub fn prepare(&mut self, include_computed: bool, hdhr_config: Option<&HdHomeRunDeviceOverview>) -> Result<(), TuliproxError> { self.prepare_templates()?; - self.prepare_sources(include_computed)?; + self.prepare_sources(include_computed, hdhr_config)?; self.check_unique_target_names()?; Ok(()) } - fn prepare_sources(&mut self, include_computed: bool) -> Result<(), TuliproxError> { + fn prepare_sources(&mut self, include_computed: bool, hdhr_config: Option<&HdHomeRunDeviceOverview>) -> Result<(), TuliproxError> { // prepare sources and set id's let mut source_index: u16 = 0; let mut target_index: u16 = 1; @@ -56,8 +56,8 @@ impl SourcesConfigDto { for target in &mut source.targets { // prepare target templates let prepare_result = match &self.templates { - Some(templ) => target.prepare(target_index, Some(templ)), - _ => target.prepare(target_index, None) + Some(templ) => target.prepare(target_index, Some(templ), hdhr_config), + _ => target.prepare(target_index, None, hdhr_config) }; prepare_result?; target_index += 1; diff --git a/shared/src/model/config/target.rs b/shared/src/model/config/target.rs index 04e12d589..314d0531b 100644 --- a/shared/src/model/config/target.rs +++ b/shared/src/model/config/target.rs @@ -1,7 +1,8 @@ +use log::warn; use crate::error::{TuliproxError, TuliproxErrorKind}; use crate::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err}; use crate::foundation::filter::{get_filter, Filter}; -use crate::model::{ClusterFlags, ConfigRenameDto, ConfigSortDto, PatternTemplate, ProcessingOrder, StrmExportStyle, TargetType, TraktConfigDto}; +use crate::model::{ClusterFlags, ConfigRenameDto, ConfigSortDto, HdHomeRunDeviceOverview, PatternTemplate, ProcessingOrder, StrmExportStyle, TargetType, TraktConfigDto}; use crate::utils::{default_as_true, default_resolve_delay_secs, default_as_default}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)] #[serde(deny_unknown_fields)] @@ -150,7 +151,7 @@ pub struct ConfigTargetDto { impl ConfigTargetDto { #[allow(clippy::too_many_lines)] - pub fn prepare(&mut self, id: u16, templates: Option<&Vec>) -> Result<(), TuliproxError> { + pub fn prepare(&mut self, id: u16, templates: Option<&Vec>, hdhr_config: Option<&HdHomeRunDeviceOverview>) -> Result<(), TuliproxError> { self.id = id; if self.output.is_empty() { return Err(info_err!(format!("Missing output format for {}", self.name))); @@ -226,6 +227,11 @@ impl ConfigTargetDto { _ => return create_tuliprox_error_result!(TuliproxErrorKind::Info, "HdHomeRun output option `use_output` only accepts `m3u` or `xtream` for target: {}", self.name), } } + if let Some(hdhr_devices) = hdhr_config { + if !hdhr_devices.devices.contains(&hdhomerun_output.device) { + return create_tuliprox_error_result!(TuliproxErrorKind::Info, "HdHomeRun output device is not defined: {}", hdhomerun_output.device); + } + } } } } @@ -248,6 +254,12 @@ impl ConfigTargetDto { if hdhomerun_needs_xtream && xtream_cnt == 0 { return create_tuliprox_error_result!(TuliproxErrorKind::Info, "HdHomeRun output has `use_output=xtream` but no `xtream` output defined: {}", self.name); } + + if let Some(hdhr_devices) = hdhr_config { + if !hdhr_devices.enabled { + warn!("You have defined an HDHomeRun output, but HDHomeRun devices are disabled."); + } + } } if let Some(watch) = &self.watch { diff --git a/webui/scss/_theme.scss b/webui/scss/_theme.scss index 5b4e18357..4265500ac 100644 --- a/webui/scss/_theme.scss +++ b/webui/scss/_theme.scss @@ -154,10 +154,12 @@ --menu-button-hover-color: #ffffff; --menu-button-hover-background-color: #202733; + --menu-button-warn-hover-color: #dd7573; + --menu-button-warn-hover-background-color: #4c3434; + --text-button-border-radius: var(--border-radius); --text-button-hover-background-color: #0d6efd; - --scrollbar-track-color: #838383; --scrollbar-handle-color: #505050;