diff --git a/Cargo.lock b/Cargo.lock index 1c3ca53..284c4f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -61,9 +62,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -76,33 +77,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -116,9 +117,9 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -165,12 +166,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -198,11 +193,17 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cassowary" @@ -212,18 +213,18 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.0.104" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" @@ -241,15 +242,16 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] [[package]] name = "clap" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -257,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -269,9 +271,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -281,15 +283,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "compact_str" @@ -331,7 +333,7 @@ dependencies = [ "crossterm_winapi", "futures-core", "libc", - "mio", + "mio 0.8.11", "parking_lot", "serde", "signal-hook", @@ -348,11 +350,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "cssparser" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.2", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -360,9 +385,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", @@ -374,9 +399,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", @@ -414,6 +439,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "diligent-date-parser" version = "0.1.4" @@ -423,6 +459,27 @@ dependencies = [ "chrono", ] +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" + [[package]] name = "either" version = "1.13.0" @@ -440,9 +497,9 @@ dependencies = [ [[package]] name = "enum-iterator" -version = "1.5.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" +checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635" dependencies = [ "enum-iterator-derive", ] @@ -510,6 +567,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.30" @@ -608,6 +675,24 @@ dependencies = [ "thread_local", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -625,25 +710,6 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.5" @@ -655,7 +721,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http", "indexmap", "slab", "tokio", @@ -680,14 +746,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "http" -version = "0.2.12" +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "html5ever" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ - "bytes", - "fnv", - "itoa", + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -703,23 +778,12 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -730,8 +794,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "pin-project-lite", ] @@ -741,48 +805,18 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "httparse", "itoa", "pin-project-lite", @@ -791,20 +825,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.29", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.2" @@ -812,15 +832,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.4.0", + "http", + "hyper", "hyper-util", - "rustls 0.23.10", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", - "webpki-roots 0.26.3", + "webpki-roots", ] [[package]] @@ -831,7 +851,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.0", + "hyper", "hyper-util", "native-tls", "tokio", @@ -848,9 +868,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.4.0", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", @@ -900,9 +920,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", @@ -935,18 +955,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -1002,27 +1013,48 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ "hashbrown", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "magnetease" -version = "0.1.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f848923411219c52ef3ada5c6d9a7ef295064ef7e9b14cb7b891d2b0c61797" +checksum = "039b3c2525ac721afb9d6cc8f9aff583bd2f0c7463e310c302e57be66af86495" dependencies = [ "async-trait", - "reqwest 0.12.5", + "reqwest", + "scraper", "serde", "serde_json", "thiserror", "tokio", ] +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.2", + "phf_codegen 0.11.2", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1056,6 +1088,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -1079,6 +1123,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "num-traits" version = "0.2.19" @@ -1090,9 +1140,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -1116,9 +1166,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1148,9 +1198,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -1199,6 +1249,96 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", +] + +[[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", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1239,9 +1379,18 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" @@ -1264,16 +1413,17 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.10", + "rustls", + "socket2", "thiserror", "tokio", "tracing", @@ -1281,15 +1431,15 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" dependencies = [ "bytes", "rand", "ring", "rustc-hash", - "rustls 0.23.10", + "rustls", "slab", "thiserror", "tinyvec", @@ -1298,9 +1448,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", @@ -1358,7 +1508,7 @@ dependencies = [ "cassowary", "compact_str", "crossterm", - "itertools 0.13.0", + "itertools", "lru", "paste", "serde", @@ -1372,18 +1522,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -1408,64 +1558,23 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg 0.50.0", -] - [[package]] name = "reqwest" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.4.0", - "hyper-rustls 0.27.2", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -1477,24 +1586,24 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.10", - "rustls-pemfile 2.1.2", + "rustls", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.3", - "winreg 0.52.0", + "webpki-roots", + "winreg", ] [[package]] @@ -1518,6 +1627,7 @@ version = "0.4.3" dependencies = [ "anyhow", "crossterm", + "magnetease", "ratatui", "rm-shared", "serde", @@ -1560,9 +1670,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" @@ -1579,46 +1689,25 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.23.10" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -1630,19 +1719,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.5" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -1654,7 +1733,7 @@ name = "rustmission" version = "0.4.3" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "chrono", "clap", "crossterm", @@ -1664,7 +1743,7 @@ dependencies = [ "open", "ratatui", "regex", - "reqwest 0.12.5", + "reqwest", "rm-config", "rm-shared", "rss", @@ -1705,20 +1784,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sct" -version = "0.7.1" +name = "scraper" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0" dependencies = [ - "ring", - "untrusted", + "ahash", + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "once_cell", + "selectors", + "tendril", ] [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -1729,28 +1814,47 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "selectors" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" +dependencies = [ + "bitflags 2.6.0", + "cssparser", + "derive_more", + "fxhash", + "log", + "new_debug_unreachable", + "phf 0.10.1", + "phf_codegen 0.10.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -1759,11 +1863,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1781,9 +1886,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -1800,6 +1905,15 @@ dependencies = [ "serde", ] +[[package]] +name = "servo_arc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -1812,12 +1926,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -1830,6 +1944,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -1863,20 +1983,52 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "stability" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", "syn", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1913,21 +2065,15 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.1" @@ -1957,30 +2103,42 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", "windows-sys 0.52.0", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -2009,9 +2167,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2024,25 +2182,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.1", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -2059,23 +2217,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls", "rustls-pki-types", "tokio", ] @@ -2095,9 +2243,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -2107,18 +2255,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -2161,21 +2309,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.32" @@ -2187,13 +2323,14 @@ dependencies = [ [[package]] name = "transmission-rpc" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbec9f959829f5f2088246adbca5f1d36120cb173cdb20ed86dcfdd58659ff8" +checksum = "975758b6dd51b367e4454e9d4ae9b61a5419c6454822ffe1ab92c29bd6196a58" dependencies = [ + "chrono", "enum-iterator", "log", - "reqwest 0.11.27", + "reqwest", "serde", "serde_repr", ] @@ -2253,11 +2390,12 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-truncate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools 0.12.1", + "itertools", + "unicode-segmentation", "unicode-width", ] @@ -2285,6 +2423,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2299,9 +2443,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" @@ -2394,12 +2538,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.3" @@ -2581,23 +2719,13 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winreg" version = "0.52.0" @@ -2620,6 +2748,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] diff --git a/Cargo.toml b/Cargo.toml index 6fb29ee..1468a03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ license = "GPL-3.0-or-later" rm-config = { version = "0.4", path = "rm-config" } rm-shared = { version = "0.4", path = "rm-shared" } -magnetease = "0.1" +magnetease = "0.3" anyhow = "1" serde = { version = "1", features = ["derive"] } transmission-rpc = "0.4" diff --git a/rm-config/Cargo.toml b/rm-config/Cargo.toml index f903d13..3096816 100644 --- a/rm-config/Cargo.toml +++ b/rm-config/Cargo.toml @@ -21,3 +21,4 @@ ratatui.workspace = true crossterm.workspace = true thiserror.workspace = true transmission-rpc.workspace = true +magnetease.workspace = true diff --git a/rm-config/defaults/config.toml b/rm-config/defaults/config.toml index 7d87ef1..f257403 100644 --- a/rm-config/defaults/config.toml +++ b/rm-config/defaults/config.toml @@ -33,3 +33,8 @@ free_space_refresh = 10 # Padding, UploadRatio, UploadedEver, AddedDate, ActivityDate, PeersConnected # SmallStatus headers = ["Name", "SizeWhenDone", "Progress", "DownloadRate", "UploadRate"] + +[search_tab] +# If you uncomment this, providers won't be automatically added in future +# versions of Rustmission. +# providers = ["Knaben", "Nyaa"] diff --git a/rm-config/defaults/keymap.toml b/rm-config/defaults/keymap.toml index ca16e66..c0b2d50 100644 --- a/rm-config/defaults/keymap.toml +++ b/rm-config/defaults/keymap.toml @@ -1,7 +1,7 @@ [general] keybindings = [ - { on = "?", action = "ShowHelp" }, - { on = "F1", action = "ShowHelp" }, + { on = "?", action = "ShowHelp", show_in_help = false }, + { on = "F1", action = "ShowHelp", show_in_help = false }, { on = "q", action = "Quit" }, { on = "Esc", action = "Close" }, @@ -16,8 +16,8 @@ keybindings = [ { on = "Home", action = "GoToBeginning" }, { on = "End", action = "GoToEnd" }, - { on = "PageUp", action = "ScrollPageUp" }, - { on = "PageDown", action = "ScrollPageDown" }, + { on = "PageUp", action = "ScrollPageUp", show_in_help = false }, + { on = "PageDown", action = "ScrollPageDown", show_in_help = false }, { modifier = "Ctrl", on = "u", action = "ScrollPageUp" }, { modifier = "Ctrl", on = "d", action = "ScrollPageDown" }, @@ -46,3 +46,9 @@ keybindings = [ { on = "d", action = "DeleteWithoutFiles" }, { on = "D", action = "DeleteWithFiles" }, ] + +[search_tab] +keybindings = [ + { on = "p", action = "ShowProvidersInfo" } +] + diff --git a/rm-config/src/keymap/actions/mod.rs b/rm-config/src/keymap/actions/mod.rs index 76131db..3436448 100644 --- a/rm-config/src/keymap/actions/mod.rs +++ b/rm-config/src/keymap/actions/mod.rs @@ -1,6 +1,7 @@ use rm_shared::action::Action; pub mod general; +pub mod search_tab; pub mod torrents_tab; pub trait UserAction: Into { diff --git a/rm-config/src/keymap/actions/search_tab.rs b/rm-config/src/keymap/actions/search_tab.rs new file mode 100644 index 0000000..9231308 --- /dev/null +++ b/rm-config/src/keymap/actions/search_tab.rs @@ -0,0 +1,25 @@ +use rm_shared::action::Action; +use serde::{Deserialize, Serialize}; + +use super::UserAction; + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SearchAction { + ShowProvidersInfo, +} + +impl UserAction for SearchAction { + fn desc(&self) -> &'static str { + match self { + SearchAction::ShowProvidersInfo => "show providers info", + } + } +} + +impl From for Action { + fn from(value: SearchAction) -> Self { + match value { + SearchAction::ShowProvidersInfo => Action::ShowProvidersInfo, + } + } +} diff --git a/rm-config/src/keymap/mod.rs b/rm-config/src/keymap/mod.rs index d7d6658..74c34cf 100644 --- a/rm-config/src/keymap/mod.rs +++ b/rm-config/src/keymap/mod.rs @@ -4,14 +4,15 @@ use std::{ collections::HashMap, io::ErrorKind, marker::PhantomData, path::PathBuf, sync::OnceLock, }; -use anyhow::Result; +use actions::search_tab::SearchAction; +use anyhow::{Context, Result}; use crossterm::event::{KeyCode, KeyModifiers as CrosstermKeyModifiers}; use serde::{ de::{self, Visitor}, Deserialize, Serialize, }; -use crate::utils; +use crate::utils::{self, ConfigFetchingError}; use rm_shared::action::Action; use self::actions::{general::GeneralAction, torrents_tab::TorrentsAction}; @@ -20,8 +21,13 @@ use self::actions::{general::GeneralAction, torrents_tab::TorrentsAction}; pub struct KeymapConfig { pub general: KeybindsHolder, pub torrents_tab: KeybindsHolder, + pub search_tab: KeybindsHolder, #[serde(skip)] - pub keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>, + pub general_keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>, + #[serde(skip)] + pub torrent_keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>, + #[serde(skip)] + pub search_keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>, } #[derive(Serialize, Deserialize, Clone)] @@ -35,6 +41,7 @@ pub struct Keybinding> { #[serde(default)] pub modifier: KeyModifier, pub action: T, + pub show_in_help: bool, } impl> Keybinding { @@ -84,11 +91,12 @@ impl> Keybinding { } impl> Keybinding { - fn new(on: KeyCode, action: T, modifier: Option) -> Self { + fn new(on: KeyCode, action: T, modifier: Option, show_in_help: bool) -> Self { Self { on, modifier: modifier.unwrap_or(KeyModifier::None), action, + show_in_help, } } } @@ -99,11 +107,12 @@ impl<'de, T: Into + Deserialize<'de>> Deserialize<'de> for Keybinding D: serde::Deserializer<'de>, { #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "lowercase")] + #[serde(field_identifier, rename_all = "snake_case")] enum Field { On, Modifier, Action, + ShowInHelp, } struct KeybindingVisitor { @@ -124,6 +133,7 @@ impl<'de, T: Into + Deserialize<'de>> Deserialize<'de> for Keybinding let mut on = None; let mut modifier = None; let mut action = None; + let mut show_in_help = None; while let Some(key) = map.next_key()? { match key { Field::On => { @@ -181,11 +191,18 @@ impl<'de, T: Into + Deserialize<'de>> Deserialize<'de> for Keybinding } action = Some(map.next_value()); } + Field::ShowInHelp => { + if show_in_help.is_some() { + return Err(de::Error::duplicate_field("action")); + } + show_in_help = Some(map.next_value()); + } } } let on = on.ok_or_else(|| de::Error::missing_field("on"))?; let action = action.ok_or_else(|| de::Error::missing_field("action"))??; let modifier = modifier.transpose().unwrap(); + let show_in_help = show_in_help.transpose().unwrap().unwrap_or(true); if modifier.is_some() { if let KeyCode::Char(char) = on { @@ -197,7 +214,7 @@ impl<'de, T: Into + Deserialize<'de>> Deserialize<'de> for Keybinding } } - Ok(Keybinding::new(on, action, modifier)) + Ok(Keybinding::new(on, action, modifier, show_in_help)) } } @@ -269,12 +286,18 @@ impl KeymapConfig { Ok(keymap_config) } Err(e) => match e { - utils::ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => { + ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => { let mut keymap_config = utils::put_config::(Self::DEFAULT_CONFIG, Self::FILENAME)?; keymap_config.populate_hashmap(); Ok(keymap_config) } + ConfigFetchingError::Toml(e) => Err(e).with_context(|| { + format!( + "Failed to parse config located at {:?}", + utils::get_config_path(Self::FILENAME) + ) + }), _ => anyhow::bail!(e), }, } @@ -293,6 +316,11 @@ impl KeymapConfig { keys.push(keybinding.keycode_string()); } } + for keybinding in &self.search_tab.keybindings { + if action == keybinding.action.into() { + keys.push(keybinding.keycode_string()); + } + } if keys.is_empty() { None @@ -304,11 +332,18 @@ impl KeymapConfig { fn populate_hashmap(&mut self) { for keybinding in &self.general.keybindings { let hash_value = (keybinding.on, keybinding.modifier.into()); - self.keymap.insert(hash_value, keybinding.action.into()); + self.general_keymap + .insert(hash_value, keybinding.action.into()); } for keybinding in &self.torrents_tab.keybindings { let hash_value = (keybinding.on, keybinding.modifier.into()); - self.keymap.insert(hash_value, keybinding.action.into()); + self.torrent_keymap + .insert(hash_value, keybinding.action.into()); + } + for keybinding in &self.search_tab.keybindings { + let hash_value = (keybinding.on, keybinding.modifier.into()); + self.search_keymap + .insert(hash_value, keybinding.action.into()); } } diff --git a/rm-config/src/lib.rs b/rm-config/src/lib.rs index 59347e3..73eaff6 100644 --- a/rm-config/src/lib.rs +++ b/rm-config/src/lib.rs @@ -12,6 +12,7 @@ pub struct Config { pub general: main_config::General, pub connection: main_config::Connection, pub torrents_tab: main_config::TorrentsTab, + pub search_tab: main_config::SearchTab, pub keybindings: KeymapConfig, pub directories: Directories, } @@ -35,6 +36,7 @@ impl Config { general: main_config.general, connection: main_config.connection, torrents_tab: main_config.torrents_tab, + search_tab: main_config.search_tab, keybindings: keybindings.clone(), directories, }) diff --git a/rm-config/src/main_config.rs b/rm-config/src/main_config.rs index a0571ac..3103966 100644 --- a/rm-config/src/main_config.rs +++ b/rm-config/src/main_config.rs @@ -1,12 +1,13 @@ use std::{io::ErrorKind, path::PathBuf, sync::OnceLock}; -use anyhow::Result; +use anyhow::{Context, Result}; +use magnetease::WhichProvider; use ratatui::style::Color; use rm_shared::header::Header; use serde::Deserialize; use url::Url; -use crate::utils::{self}; +use crate::utils::{self, ConfigFetchingError}; #[derive(Deserialize)] pub struct MainConfig { @@ -14,6 +15,8 @@ pub struct MainConfig { pub connection: Connection, #[serde(default)] pub torrents_tab: TorrentsTab, + #[serde(default)] + pub search_tab: SearchTab, } #[derive(Deserialize)] @@ -78,6 +81,23 @@ impl Default for TorrentsTab { } } +#[derive(Deserialize)] +pub struct SearchTab { + pub providers: Vec, +} + +fn default_providers() -> Vec { + vec![WhichProvider::Knaben, WhichProvider::Nyaa] +} + +impl Default for SearchTab { + fn default() -> Self { + Self { + providers: default_providers(), + } + } +} + impl MainConfig { pub(crate) const FILENAME: &'static str = "config.toml"; const DEFAULT_CONFIG: &'static str = include_str!("../defaults/config.toml"); @@ -86,11 +106,17 @@ impl MainConfig { match utils::fetch_config::(Self::FILENAME) { Ok(config) => Ok(config), Err(e) => match e { - utils::ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => { + ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => { utils::put_config::(Self::DEFAULT_CONFIG, Self::FILENAME)?; println!("Update {:?} and start rustmission again", Self::path()); std::process::exit(0); } + ConfigFetchingError::Toml(e) => Err(e).with_context(|| { + format!( + "Failed to parse config located at {:?}", + utils::get_config_path(Self::FILENAME) + ) + }), _ => anyhow::bail!(e), }, } diff --git a/rm-main/src/app.rs b/rm-main/src/app.rs index fcd1570..7a20a69 100644 --- a/rm-main/src/app.rs +++ b/rm-main/src/app.rs @@ -6,6 +6,7 @@ use rm_shared::action::Action; use rm_shared::action::UpdateAction; use std::sync::Arc; +use crate::ui::components::tabs::CurrentTab; use crate::{ transmission::{self, TorrentAction}, tui::Tui, @@ -127,11 +128,13 @@ impl App { let update_action = self.update_rx.recv(); let tick_action = interval.tick(); + let current_tab = self.main_window.tabs.current_tab; + tokio::select! { _ = tick_action => self.tick(), event = tui_event => { - event_to_action(&self.ctx, self.mode, event.unwrap()); + event_to_action(&self.ctx, self.mode, current_tab, event.unwrap()); }, update_action = update_action => self.handle_update_action(update_action.unwrap()).await, @@ -140,8 +143,6 @@ impl App { if let Some(action) = action { if action.is_render() { self.render(tui)?; - } else if action.is_quit() { - self.should_quit = true; } else { self.handle_user_action(action).await } @@ -201,13 +202,7 @@ pub enum Mode { Normal, } -pub fn event_to_action( - ctx: &Ctx, - mode: Mode, - event: Event, - // sender: &UnboundedSender, - // keymap: &HashMap<(KeyCode, KeyModifiers), Action>, -) { +pub fn event_to_action(ctx: &Ctx, mode: Mode, current_tab: CurrentTab, event: Event) { // Handle CTRL+C first if let Event::Key(key_event) = event { if key_event.modifiers == KeyModifiers::CONTROL @@ -220,27 +215,34 @@ pub fn event_to_action( match event { Event::Key(key) if mode == Mode::Input => ctx.send_action(Action::Input(key)), Event::Key(key) => { - if let KeyCode::Char(e) = key.code { - if e.is_uppercase() { - if let Some(action) = ctx - .config - .keybindings - .keymap - .get(&(key.code, KeyModifiers::NONE)) - .cloned() - { - ctx.send_action(action); - } + let keymaps = match current_tab { + CurrentTab::Torrents => [ + &ctx.config.keybindings.general_keymap, + &ctx.config.keybindings.torrent_keymap, + ], + CurrentTab::Search => [ + &ctx.config.keybindings.general_keymap, + &ctx.config.keybindings.search_keymap, + ], + }; + + let keybinding = match key.code { + KeyCode::Char(e) => { + let modifier = if e.is_uppercase() { + KeyModifiers::NONE + } else { + key.modifiers + }; + (key.code, modifier) + } + _ => (key.code, key.modifiers), + }; + + for keymap in keymaps { + if let Some(action) = keymap.get(&keybinding).cloned() { + ctx.send_action(action); + return; } - } - if let Some(action) = ctx - .config - .keybindings - .keymap - .get(&(key.code, key.modifiers)) - .cloned() - { - ctx.send_action(action); } } Event::Resize(_, _) => ctx.send_action(Action::Render), diff --git a/rm-main/src/ui/components/table.rs b/rm-main/src/ui/components/table.rs index 824515a..6b5177c 100644 --- a/rm-main/src/ui/components/table.rs +++ b/rm-main/src/ui/components/table.rs @@ -50,6 +50,8 @@ impl GenericTable { } else { state.select(Some(curr + 1)); } + } else { + state.select(Some(0)); } } @@ -67,6 +69,8 @@ impl GenericTable { } else { state.select(Some(curr - 1)); } + } else { + state.select(Some(0)); } } diff --git a/rm-main/src/ui/global_popups/help.rs b/rm-main/src/ui/global_popups/help.rs index 57d2021..85e71df 100644 --- a/rm-main/src/ui/global_popups/help.rs +++ b/rm-main/src/ui/global_popups/help.rs @@ -59,15 +59,49 @@ impl HelpPopup { lines: &mut Vec, ) { let mut keys = BTreeMap::new(); + let mut max_len = 0; for keybinding in keybindings { + if !keybinding.show_in_help { + continue; + } + + let keycode = keybinding.keycode_string(); + if keycode.len() > max_len { + max_len = keycode.chars().count(); + } + keys.entry(&keybinding.action) .or_insert_with(Vec::new) .push(keybinding.keycode_string()); } + for (_, keycodes) in &keys { + let delimiter_len; + let mut keycodes_total_len = 0; + if keycodes.len() >= 2 { + delimiter_len = (keycodes.len() - 1) * 3; + } else { + delimiter_len = 0; + } + + for keycode in keycodes { + keycodes_total_len += keycode.chars().count(); + } + + if keycodes_total_len + delimiter_len > max_len { + max_len = keycodes_total_len + delimiter_len; + } + } + for (action, keycodes) in keys { - let keycode_string = keycodes.join(" / "); + let mut keycode_string = keycodes.join(" / "); + let mut how_much_to_pad = max_len - keycode_string.chars().count(); + while how_much_to_pad > 0 { + keycode_string.insert(0, ' '); + how_much_to_pad -= 1; + } + add_line!(lines, keycode_string, action.desc()); } } @@ -167,6 +201,19 @@ impl Component for HelpPopup { &mut lines, ); + lines.push( + Line::from(vec![Span::styled( + "Search Tab", + Style::default().bold().underlined(), + )]) + .centered(), + ); + + Self::write_keybindings( + &self.ctx.config.keybindings.search_tab.keybindings, + &mut lines, + ); + let help_text = Text::from(lines); if text_rect.height <= u16::try_from(help_text.lines.len()).unwrap() { diff --git a/rm-main/src/ui/mod.rs b/rm-main/src/ui/mod.rs index 21741de..0b9451c 100644 --- a/rm-main/src/ui/mod.rs +++ b/rm-main/src/ui/mod.rs @@ -20,7 +20,7 @@ use self::{ }; pub struct MainWindow { - tabs: TabComponent, + pub tabs: TabComponent, torrents_tab: TorrentsTab, search_tab: SearchTab, global_popup_manager: GlobalPopupManager, diff --git a/rm-main/src/ui/tabs/search/bottom_bar.rs b/rm-main/src/ui/tabs/search/bottom_bar.rs new file mode 100644 index 0000000..6a9b70a --- /dev/null +++ b/rm-main/src/ui/tabs/search/bottom_bar.rs @@ -0,0 +1,159 @@ +use ratatui::{ + layout::Rect, + style::{Style, Stylize}, + text::{Line, Span}, + widgets::Paragraph, + Frame, +}; +use rm_shared::action::{Action, UpdateAction}; +use throbber_widgets_tui::ThrobberState; + +use crate::{app, ui::components::Component}; + +use super::{ConfiguredProvider, ProviderState}; + +pub struct BottomBar { + pub search_state: SearchState, +} + +impl BottomBar { + pub fn new(ctx: app::Ctx, providers: &Vec) -> Self { + Self { + search_state: SearchState::new(ctx, providers), + } + } +} + +impl Component for BottomBar { + fn render(&mut self, f: &mut Frame, rect: Rect) { + self.search_state.render(f, rect); + } + + fn tick(&mut self) { + self.search_state.tick(); + } +} + +pub struct SearchState { + ctx: app::Ctx, + stage: SearchStage, + providers_finished: u8, + providers_errored: u8, + providers_count: u8, +} + +#[derive(Clone)] +enum SearchStage { + Nothing, + NoResults, + Searching(ThrobberState), + Found(usize), +} + +impl SearchState { + fn new(ctx: app::Ctx, providers: &Vec) -> Self { + let mut providers_count = 0u8; + for provider in providers { + if provider.enabled { + providers_count += 1; + } + } + + Self { + ctx, + stage: SearchStage::Nothing, + providers_errored: 0, + providers_finished: 0, + providers_count, + } + } + + pub fn update_counts(&mut self, providers: &Vec) { + for provider in providers { + if provider.enabled { + if matches!(provider.provider_state, ProviderState::Found(_)) { + self.providers_finished += 1; + } else if matches!(provider.provider_state, ProviderState::Error(_)) { + self.providers_errored += 1; + } + } + } + } + + pub fn searching(&mut self) { + self.stage = SearchStage::Searching(ThrobberState::default()); + } + + pub fn not_found(&mut self) { + self.stage = SearchStage::NoResults; + } + + pub fn found(&mut self, count: usize) { + self.stage = SearchStage::Found(count); + } +} + +impl Component for SearchState { + fn handle_update_action(&mut self, action: UpdateAction) { + if let UpdateAction::SearchStarted = action { + self.searching(); + }; + } + + fn render(&mut self, f: &mut Frame, rect: Rect) { + let append_key_info = |line: &mut Line| { + let providers_key = self + .ctx + .config + .keybindings + .get_keys_for_action(Action::ShowProvidersInfo); + if let Some(key) = providers_key { + line.push_span(Span::raw("Press ")); + line.push_span(Span::styled( + key, + Style::default() + .fg(self.ctx.config.general.accent_color) + .underlined(), + )); + line.push_span(Span::raw(" for details.")) + } + }; + + match &mut self.stage { + SearchStage::Nothing => (), + SearchStage::Searching(ref mut state) => { + let label = format!( + "Searching... {:.0}%", + self.providers_finished / self.providers_count + ); + let default_throbber = throbber_widgets_tui::Throbber::default() + .label(label) + .style(ratatui::style::Style::default().fg(ratatui::style::Color::Yellow)); + f.render_stateful_widget(default_throbber.clone(), rect, state); + } + SearchStage::NoResults => { + let mut line = Line::default(); + line.push_span(Span::styled("", Style::default().red())); + line.push_span(Span::raw(" No results. ")); + append_key_info(&mut line); + let paragraph = Paragraph::new(line); + f.render_widget(paragraph, rect); + } + SearchStage::Found(count) => { + let mut line = Line::default(); + line.push_span(Span::styled("", Style::default().green())); + line.push_span(Span::raw(format!(" Found {count}. "))); + append_key_info(&mut line); + let paragraph = Paragraph::new(line); + f.render_widget(paragraph, rect); + } + } + } + + fn tick(&mut self) { + if let SearchStage::Searching(state) = &mut self.stage { + state.calc_next(); + self.ctx.send_action(Action::Render); + } + } +} diff --git a/rm-main/src/ui/tabs/search.rs b/rm-main/src/ui/tabs/search/mod.rs similarity index 57% rename from rm-main/src/ui/tabs/search.rs rename to rm-main/src/ui/tabs/search/mod.rs index fa21d28..48ba645 100644 --- a/rm-main/src/ui/tabs/search.rs +++ b/rm-main/src/ui/tabs/search/mod.rs @@ -1,13 +1,19 @@ -use std::borrow::Cow; +mod bottom_bar; +mod popups; +use std::{borrow::Cow, sync::Arc}; + +use bottom_bar::BottomBar; use crossterm::event::{KeyCode, KeyEvent}; -use magnetease::{Magnet, Magnetease}; +use futures::{stream::FuturesUnordered, StreamExt}; +use magnetease::{Magnet, MagneteaseErrorKind, WhichProvider}; +use popups::{CurrentPopup, PopupManager}; use ratatui::{ layout::Flex, prelude::*, widgets::{Cell, Paragraph, Row, Table}, }; -use throbber_widgets_tui::ThrobberState; +use reqwest::Client; use tokio::sync::mpsc::{self, UnboundedSender}; use tui_input::Input; @@ -35,8 +41,9 @@ pub(crate) struct SearchTab { input: Input, search_query_rx: UnboundedSender, table: GenericTable, - // TODO: Change it to enum, and combine table with search_result_info - search_result_info: SearchResultState, + popup_manager: PopupManager, + configured_providers: Vec, + bottom_bar: BottomBar, currently_displaying_no: u16, ctx: app::Ctx, } @@ -45,16 +52,49 @@ impl SearchTab { pub(crate) fn new(ctx: app::Ctx) -> Self { let (search_query_tx, mut search_query_rx) = mpsc::unbounded_channel::(); let table = GenericTable::new(vec![]); - let search_result_info = SearchResultState::new(ctx.clone()); + + let mut configured_providers = vec![]; + + for provider in WhichProvider::all() { + configured_providers.push(ConfiguredProvider::new(provider.clone(), false)); + } + + for configured_provider in &mut configured_providers { + if ctx + .config + .search_tab + .providers + .contains(&configured_provider.provider) + { + configured_provider.enabled = true; + } + } + + let bottom_bar = BottomBar::new(ctx.clone(), &configured_providers); tokio::task::spawn({ let ctx = ctx.clone(); + let configured_providers = configured_providers.clone(); async move { - let magnetease = Magnetease::new(); - while let Some(search_phrase) = search_query_rx.recv().await { + let client = Client::new(); + while let Some(phrase) = search_query_rx.recv().await { ctx.send_update_action(UpdateAction::SearchStarted); - let magnets = magnetease.search(&search_phrase).await.unwrap(); - ctx.send_update_action(UpdateAction::SearchResults(magnets)); + let mut futures = FuturesUnordered::new(); + for configured_provider in &configured_providers { + if configured_provider.enabled { + futures.push(configured_provider.provider.search(&client, &phrase)); + } + } + + while let Some(result) = futures.next().await { + match result { + Ok(response) => { + ctx.send_update_action(UpdateAction::ProviderResult(response)) + } + Err(e) => ctx.send_update_action(UpdateAction::ProviderError(e)), + } + } + ctx.send_update_action(UpdateAction::SearchFinished); } } }); @@ -63,10 +103,12 @@ impl SearchTab { focus: SearchTabFocus::List, input: Input::default(), table, - search_result_info, + bottom_bar, search_query_rx: search_query_tx, currently_displaying_no: 0, + popup_manager: PopupManager::new(ctx.clone()), ctx, + configured_providers, } } @@ -162,11 +204,59 @@ impl SearchTab { let _ = open::that_detached(&magnet.url); } } + + fn show_providers_info(&mut self) { + self.popup_manager + .show_providers_info_popup(self.configured_providers.clone()); + self.ctx.send_action(Action::Render); + } + + fn providers_searching(&mut self) { + for configured_provider in &mut self.configured_providers { + if configured_provider.enabled { + configured_provider.provider_state = ProviderState::Searching; + } + } + } + + fn provider_state_success(&mut self, provider: WhichProvider, results_count: u16) { + for configured_provider in &mut self.configured_providers { + if configured_provider.provider == provider { + configured_provider.provider_state = ProviderState::Found(results_count); + break; + } + } + } + + fn provider_state_error(&mut self, provider: WhichProvider, error: MagneteaseErrorKind) { + for configured_provider in &mut self.configured_providers { + if configured_provider.provider == provider { + configured_provider.provider_state = ProviderState::Error(Arc::new(error)); + break; + } + } + } + + fn update_providers_popup(&mut self) { + if let Some(CurrentPopup::Providers(popup)) = &mut self.popup_manager.current_popup { + popup.update_providers(self.configured_providers.clone()); + } + } } impl Component for SearchTab { fn handle_actions(&mut self, action: Action) -> ComponentAction { use Action as A; + + if self.popup_manager.is_showing_popup() { + self.popup_manager.handle_actions(action); + return ComponentAction::Nothing; + } + + if action.is_quit() { + self.ctx.send_action(Action::HardQuit); + } + match action { A::Quit => self.ctx.send_action(Action::Quit), A::Search => self.start_search(), @@ -180,6 +270,7 @@ impl Component for SearchTab { A::End => self.scroll_to_end(), A::Confirm => self.add_torrent(), A::XdgOpen => self.xdg_open(), + A::ShowProvidersInfo => self.show_providers_info(), _ => (), }; @@ -189,22 +280,55 @@ impl Component for SearchTab { fn handle_update_action(&mut self, action: UpdateAction) { match action { UpdateAction::SearchStarted => { - self.search_result_info.searching(ThrobberState::default()); + self.providers_searching(); + + self.table.items.drain(..); + self.table.state.borrow_mut().select(None); + + self.bottom_bar + .handle_update_action(UpdateAction::SearchStarted); + self.update_providers_popup(); + } + UpdateAction::ProviderResult(response) => { + self.provider_state_success( + response.provider, + u16::try_from(response.magnets.len()).unwrap(), + ); + + self.bottom_bar + .search_state + .update_counts(&self.configured_providers); + self.update_providers_popup(); + + self.table.items.extend(response.magnets); + self.table.items.sort_by(|a, b| b.seeders.cmp(&a.seeders)); + + let mut state = self.table.state.borrow_mut(); + if !self.table.items.is_empty() && state.selected().is_none() { + state.select(Some(0)) + } + } + UpdateAction::ProviderError(e) => { + self.provider_state_error(e.provider, e.kind); + + self.bottom_bar + .search_state + .update_counts(&self.configured_providers); + self.update_providers_popup(); } - UpdateAction::SearchResults(magnets) => { - if magnets.is_empty() { - self.search_result_info.not_found(); + UpdateAction::SearchFinished => { + if self.table.items.is_empty() { + self.bottom_bar.search_state.not_found(); } else { - self.search_result_info.found(magnets.len()); + self.bottom_bar.search_state.found(self.table.items.len()); } - self.table.set_items(magnets); } _ => (), } } fn tick(&mut self) { - self.search_result_info.tick(); + self.bottom_bar.tick(); } fn render(&mut self, f: &mut Frame, rect: Rect) { @@ -285,76 +409,32 @@ impl Component for SearchTab { f.render_stateful_widget(table, rest, &mut self.table.state.borrow_mut()); - self.search_result_info.render(f, bottom_line); + self.bottom_bar.render(f, bottom_line); + self.popup_manager.render(f, f.size()); } } #[derive(Clone)] -enum SearchResultStatus { - Nothing, - NoResults, - Searching(ThrobberState), - Found(usize), +pub struct ConfiguredProvider { + provider: WhichProvider, + provider_state: ProviderState, + enabled: bool, } -#[derive(Clone)] -struct SearchResultState { - ctx: app::Ctx, - status: SearchResultStatus, -} - -impl SearchResultState { - fn new(ctx: app::Ctx) -> Self { +impl ConfiguredProvider { + fn new(provider: WhichProvider, enabled: bool) -> Self { Self { - ctx, - status: SearchResultStatus::Nothing, + provider, + provider_state: ProviderState::Idle, + enabled, } } - - fn searching(&mut self, state: ThrobberState) { - self.status = SearchResultStatus::Searching(state); - } - - fn not_found(&mut self) { - self.status = SearchResultStatus::NoResults; - } - - fn found(&mut self, count: usize) { - self.status = SearchResultStatus::Found(count); - } } -impl Component for SearchResultState { - fn render(&mut self, f: &mut Frame, rect: Rect) { - match &mut self.status { - SearchResultStatus::Nothing => (), - SearchResultStatus::Searching(ref mut state) => { - let default_throbber = throbber_widgets_tui::Throbber::default() - .label("Searching...") - .style(ratatui::style::Style::default().fg(ratatui::style::Color::Yellow)); - f.render_stateful_widget(default_throbber.clone(), rect, state); - } - SearchResultStatus::NoResults => { - let mut line = Line::default(); - line.push_span(Span::styled("", Style::default().red())); - line.push_span(Span::raw(" No results")); - let paragraph = Paragraph::new(line); - f.render_widget(paragraph, rect); - } - SearchResultStatus::Found(count) => { - let mut line = Line::default(); - line.push_span(Span::styled("", Style::default().green())); - line.push_span(Span::raw(format!(" Found {count}"))); - let paragraph = Paragraph::new(line); - f.render_widget(paragraph, rect); - } - } - } - - fn tick(&mut self) { - if let SearchResultStatus::Searching(state) = &mut self.status { - state.calc_next(); - self.ctx.send_action(Action::Render); - } - } +#[derive(Clone)] +enum ProviderState { + Idle, + Searching, + Found(u16), + Error(Arc), } diff --git a/rm-main/src/ui/tabs/search/popups/mod.rs b/rm-main/src/ui/tabs/search/popups/mod.rs new file mode 100644 index 0000000..2521844 --- /dev/null +++ b/rm-main/src/ui/tabs/search/popups/mod.rs @@ -0,0 +1,75 @@ +mod providers; + +use providers::ProvidersPopup; +use ratatui::prelude::*; +use ratatui::Frame; +use rm_shared::action::Action; + +use crate::{ + app, + ui::components::{Component, ComponentAction}, +}; + +use super::ConfiguredProvider; + +pub struct PopupManager { + ctx: app::Ctx, + pub current_popup: Option, +} + +pub enum CurrentPopup { + Providers(ProvidersPopup), +} + +impl PopupManager { + pub const fn new(ctx: app::Ctx) -> Self { + Self { + ctx, + current_popup: None, + } + } + + pub const fn is_showing_popup(&self) -> bool { + self.current_popup.is_some() + } + + fn show_popup(&mut self, popup: CurrentPopup) { + self.current_popup = Some(popup); + } + + pub fn show_providers_info_popup(&mut self, providers: Vec) { + self.show_popup(CurrentPopup::Providers(ProvidersPopup::new( + self.ctx.clone(), + providers, + ))); + } + + pub fn close_popup(&mut self) { + self.current_popup = None; + } +} + +impl Component for PopupManager { + fn handle_actions(&mut self, action: Action) -> ComponentAction { + if let Some(current_popup) = &mut self.current_popup { + match current_popup { + CurrentPopup::Providers(popup) => { + if popup.handle_actions(action).is_quit() { + self.close_popup(); + self.ctx.send_action(Action::Render); + } + } + } + } + + ComponentAction::Nothing + } + + fn render(&mut self, f: &mut Frame, rect: Rect) { + if let Some(popup) = &mut self.current_popup { + match popup { + CurrentPopup::Providers(popup) => popup.render(f, rect), + } + } + } +} diff --git a/rm-main/src/ui/tabs/search/popups/providers.rs b/rm-main/src/ui/tabs/search/popups/providers.rs new file mode 100644 index 0000000..5219a30 --- /dev/null +++ b/rm-main/src/ui/tabs/search/popups/providers.rs @@ -0,0 +1,125 @@ +use magnetease::ProviderCategory; +use ratatui::{ + layout::{Alignment, Constraint, Margin}, + prelude::Rect, + style::{Style, Styled, Stylize}, + text::{Line, ToLine}, + widgets::{ + block::{Position, Title}, + Block, BorderType, Clear, Row, Table, + }, + Frame, +}; +use rm_shared::action::Action; + +use crate::{ + app, + ui::{ + centered_rect, + components::{Component, ComponentAction}, + tabs::search::{ConfiguredProvider, ProviderState}, + }, +}; + +pub struct ProvidersPopup { + ctx: app::Ctx, + providers: Vec, +} + +impl From<&ConfiguredProvider> for Row<'_> { + fn from(value: &ConfiguredProvider) -> Self { + let mut name: Line = match value.provider_state { + _ if !value.enabled => " 󰪎 ".into(), + ProviderState::Idle => " 󱗼 ".yellow().into(), + ProviderState::Searching => "  ".yellow().into(), + ProviderState::Found(_) => "  ".green().into(), + ProviderState::Error(_) => "  ".red().into(), + }; + + name.push_span(value.provider.name()); + + let category: Line = match value.provider.category() { + ProviderCategory::General => " General".to_line(), + ProviderCategory::Anime => "󰎁 Anime".to_line(), + }; + + let url: Line = format!("({})", value.provider.display_url()).into(); + + let status: Line = match &value.provider_state { + _ if !value.enabled => "Disabled".into(), + ProviderState::Idle => "Idle".into(), + ProviderState::Searching => " Searching...".yellow().into(), + ProviderState::Found(count) => { + let mut line = Line::default(); + line.push_span("Found("); + line.push_span(count.to_string().green()); + line.push_span(")"); + line + } + ProviderState::Error(e) => e.to_string().red().into(), + }; + + let row = Row::new(vec![name, url, category, status]); + + if value.enabled { + row + } else { + row.dark_gray() + } + } +} + +impl ProvidersPopup { + pub const fn new(ctx: app::Ctx, providers: Vec) -> Self { + Self { ctx, providers } + } + + pub fn update_providers(&mut self, providers: Vec) { + self.providers = providers; + } +} + +impl Component for ProvidersPopup { + fn handle_actions(&mut self, action: Action) -> ComponentAction { + match action { + _ if action.is_soft_quit() => ComponentAction::Quit, + Action::Confirm => ComponentAction::Quit, + _ => ComponentAction::Nothing, + } + } + + fn render(&mut self, f: &mut Frame, rect: Rect) { + let popup_rect = centered_rect(rect, 80, 50); + let block_rect = popup_rect.inner(Margin::new(1, 1)); + let table_rect = block_rect.inner(Margin::new(1, 1)); + + let title_style = Style::default().fg(self.ctx.config.general.accent_color); + let block = Block::bordered() + .border_type(BorderType::Rounded) + .title(Title::from(" Providers ".set_style(title_style))) + .title( + Title::from(" [ CLOSE ] ".set_style(title_style.bold())) + .alignment(Alignment::Right) + .position(Position::Bottom), + ); + + let widths = [ + Constraint::Length(10), // Provider name (and icon status prefix) + Constraint::Length(15), // Provider URL + Constraint::Length(15), // Provider category + Constraint::Length(15), // Provider stuatus + ]; + + let rows: Vec> = self + .providers + .iter() + .map(|provider| provider.into()) + .collect(); + + let table = Table::new(rows, widths); + + f.render_widget(Clear, popup_rect); + f.render_widget(block, block_rect); + f.render_widget(table, table_rect); + } +} diff --git a/rm-main/src/ui/tabs/torrents/mod.rs b/rm-main/src/ui/tabs/torrents/mod.rs index b3b8986..5f74cc4 100644 --- a/rm-main/src/ui/tabs/torrents/mod.rs +++ b/rm-main/src/ui/tabs/torrents/mod.rs @@ -66,7 +66,6 @@ impl Component for TorrentsTab { self.popup_manager.render(f, f.size()); } - #[must_use] fn handle_actions(&mut self, action: Action) -> ComponentAction { use Action as A; @@ -76,7 +75,7 @@ impl Component for TorrentsTab { } if action.is_quit() { - self.ctx.send_action(Action::Quit); + self.ctx.send_action(Action::HardQuit); } match action { diff --git a/rm-main/src/ui/tabs/torrents/popups/mod.rs b/rm-main/src/ui/tabs/torrents/popups/mod.rs index 7035786..cda27f8 100644 --- a/rm-main/src/ui/tabs/torrents/popups/mod.rs +++ b/rm-main/src/ui/tabs/torrents/popups/mod.rs @@ -37,7 +37,7 @@ impl PopupManager { } pub fn close_popup(&mut self) { - self.current_popup = None + self.current_popup = None; } } diff --git a/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs index c2c8a2f..1b1a423 100644 --- a/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs +++ b/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs @@ -52,8 +52,8 @@ impl RustmissionTorrent { let char_indices: Vec = self.torrent_name.char_indices().map(|(i, _)| i).collect(); let mut last_end = 0; let mut flush_line = |start: usize, end: usize| { - let mut start = char_indices[start as usize]; - let mut end = char_indices[end as usize]; + let mut start = char_indices[start]; + let mut end = char_indices[end]; torrent_name_line.push_span(Span::styled( &self.torrent_name[last_end..start], self.style, diff --git a/rm-main/src/ui/tabs/torrents/table_manager.rs b/rm-main/src/ui/tabs/torrents/table_manager.rs index 686450e..2a1d8a1 100644 --- a/rm-main/src/ui/tabs/torrents/table_manager.rs +++ b/rm-main/src/ui/tabs/torrents/table_manager.rs @@ -83,7 +83,7 @@ impl TableManager { if let Some(filter) = &self.filter { if filter.indexes.is_empty() { - return None; + None } else { self.table .items @@ -95,7 +95,7 @@ impl TableManager { } pub fn set_new_rows(&mut self, rows: Vec) { - self.table.items = rows; + self.table.set_items(rows); self.widths = self.header_widths(&self.table.items); self.update_rows_number(); } diff --git a/rm-shared/src/action.rs b/rm-shared/src/action.rs index c18627d..d1c0876 100644 --- a/rm-shared/src/action.rs +++ b/rm-shared/src/action.rs @@ -1,13 +1,14 @@ use std::{error::Error, sync::Arc}; use crossterm::event::KeyEvent; -use magnetease::Magnet; +use magnetease::{MagneteaseError, MagneteaseResult}; use transmission_rpc::types::{FreeSpace, SessionStats, Torrent}; use crate::status_task::StatusTask; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Action { + // General HardQuit, Quit, Close, @@ -23,22 +24,25 @@ pub enum Action { Confirm, Select, ShowHelp, + Search, + ChangeFocus, + ChangeTab(u8), + XdgOpen, + Input(KeyEvent), + // Torrents Tab ShowStats, ShowFiles, - Search, Pause, DeleteWithoutFiles, DeleteWithFiles, - ChangeFocus, AddMagnet, MoveTorrent, - ChangeTab(u8), - Input(KeyEvent), - XdgOpen, + // Search Tab + ShowProvidersInfo, } pub enum UpdateAction { - // Global + // General SwitchToInputMode, SwitchToNormalMode, Error(Box), @@ -56,7 +60,9 @@ pub enum UpdateAction { SearchFilterClear, // Search Tab SearchStarted, - SearchResults(Vec), + ProviderResult(MagneteaseResult), + ProviderError(MagneteaseError), + SearchFinished, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -85,6 +91,10 @@ impl Action { *self == Self::Render } + pub fn is_hard_quit(&self) -> bool { + *self == Self::HardQuit + } + pub fn is_quit(&self) -> bool { *self == Self::HardQuit || *self == Self::Quit }