diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..edf819b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "abstracttorrent"] + path = abstracttorrent + url = https://github.com/SeanOMik/abstracttorrent.git diff --git a/Cargo.lock b/Cargo.lock index ea6e489..d4998bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,14 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "abstracttorrent" +version = "0.1.0" +dependencies = [ + "async-trait", + "qbittorrent", +] + [[package]] name = "adler" version = "1.0.2" @@ -56,6 +64,17 @@ dependencies = [ "syn", ] +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atom_syndication" version = "0.11.0" @@ -179,6 +198,49 @@ dependencies = [ "custom_derive", ] +[[package]] +name = "cookie" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4b6aa369f41f5faa04bb80c9b1f4216ea81646ed6124d76ba5c49a7aafd9cd" +dependencies = [ + "cookie", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.2" @@ -201,6 +263,7 @@ dependencies = [ name = "cross-seed" version = "0.1.0" dependencies = [ + "abstracttorrent", "argmap", "async-recursion", "bytes 1.1.0", @@ -213,6 +276,7 @@ dependencies = [ "rss", "serde", "serde_with", + "stopwatch", "tokio 1.19.2", "toml", "torznab", @@ -421,6 +485,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "figment" version = "0.10.6" @@ -451,6 +524,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -461,6 +549,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -603,7 +697,7 @@ dependencies = [ "indexmap", "slab", "tokio 1.19.2", - "tokio-util 0.7.3", + "tokio-util", "tracing", ] @@ -613,6 +707,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -693,6 +793,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.1.0", + "hyper", + "native-tls", + "tokio 1.19.2", + "tokio-native-tls", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -712,12 +825,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.1", ] [[package]] @@ -726,6 +839,15 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -758,9 +880,9 @@ checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -871,6 +993,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.5.3" @@ -901,9 +1033,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", @@ -934,6 +1066,24 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "net2" version = "0.2.37" @@ -951,6 +1101,42 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" +dependencies = [ + "num-integer", + "num-traits", + "rand", + "rustc-serialize", +] + +[[package]] +name = "num-complex" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" +dependencies = [ + "num-traits", + "rustc-serialize", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -961,6 +1147,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "rustc-serialize", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -980,12 +1189,66 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +[[package]] +name = "openssl" +version = "0.10.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.9.0" @@ -1097,10 +1360,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "proc-macro2" -version = "1.0.39" +name = "pkg-config" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] @@ -1118,6 +1393,35 @@ dependencies = [ "yansi", ] +[[package]] +name = "psl-types" +version = "2.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8eda7c62d9ecaafdf8b62374c006de0adf61666ae96a96ba74a37134aa4e470" + +[[package]] +name = "publicsuffix" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "292972edad6bbecc137ab84c5e36421a4a6c979ea31d3cc73540dd04315b33e1" +dependencies = [ + "byteorder", + "hashbrown 0.11.2", + "idna", + "psl-types", +] + +[[package]] +name = "qbittorrent" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + [[package]] name = "quick-xml" version = "0.22.0" @@ -1140,13 +1444,50 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -1180,14 +1521,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] -name = "reqwest" -version = "0.11.10" +name = "remove_dir_all" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" dependencies = [ "async-compression", "base64", "bytes 1.1.0", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -1196,21 +1548,27 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "lazy_static", "log", "mime", + "mime_guess", + "native-tls", "percent-encoding", "pin-project-lite", + "proc-macro-hack", "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio 1.19.2", + "tokio-native-tls", "tokio-rustls", - "tokio-util 0.6.10", + "tokio-util", + "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -1246,6 +1604,12 @@ dependencies = [ "quick-xml 0.22.0", ] +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + [[package]] name = "rustc_version" version = "0.2.3" @@ -1269,9 +1633,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.3.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" dependencies = [ "base64", ] @@ -1282,6 +1646,16 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1298,6 +1672,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -1344,6 +1741,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1468,6 +1876,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stopwatch" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d04b5ebc78da44d3a456319d8bc2783e7d8cc7ccbb5cb4dc3f54afbd93bf728" +dependencies = [ + "num", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1476,15 +1893,29 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall 0.2.13", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "thiserror" version = "1.0.31" @@ -1514,6 +1945,24 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82501a4c1c0330d640a6e176a3d6a204f5ec5237aca029029d21864a902e27b0" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "tinyvec" version = "1.6.0" @@ -1562,7 +2011,7 @@ dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -1637,6 +2086,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio 1.19.2", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -1753,20 +2212,6 @@ dependencies = [ "tokio-reactor", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes 1.1.0", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio 1.19.2", -] - [[package]] name = "tokio-util" version = "0.7.3" @@ -1818,9 +2263,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" @@ -1912,6 +2357,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -1920,9 +2374,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -1969,6 +2423,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1993,9 +2453,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2003,9 +2463,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -2018,9 +2478,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2030,9 +2490,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2040,9 +2500,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -2053,15 +2513,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index b3ec59e..276b408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,18 @@ edition = "2021" tokio = { version = "1.19.2", features = ["full"] } tracing = "0.1.35" tracing-subscriber = "0.3.11" -futures = "0.3.21" +futures = { version = "0.3.21", features= ["executor"] } toml = "0.5.9" lava_torrent = "0.7.0" # https://docs.rs/lava_torrent/0.7.0/lava_torrent/ torznab = "0.7.2" # https://docs.rs/torznab/0.7.2/torznab/ + +stopwatch = "0.0.7" + magnet-url = { git = "https://github.com/SeanOMik/magnet-url-rs.git", branch = "main" } +abstracttorrent = { path = "abstracttorrent" } +#abstracttorrent = { git = "https://github.com/SeanOMik/abstracttorrent.git", branch = "main" } +#qbittorrent = { git = "https://github.com/SeanOMik/qbittorrent-rs.git", branch = "main"} + serde_with = "1.14.0" serde = { version = "1.0", features = ["derive"] } figment = { version = "0.10", features = ["toml", "env"] } diff --git a/abstracttorrent b/abstracttorrent new file mode 160000 index 0000000..2b4cd84 --- /dev/null +++ b/abstracttorrent @@ -0,0 +1 @@ +Subproject commit 2b4cd84d7faf49d37492c6ff5708454beea7857a diff --git a/src/config/client/mod.rs b/src/config/client/mod.rs new file mode 100644 index 0000000..36f84d3 --- /dev/null +++ b/src/config/client/mod.rs @@ -0,0 +1 @@ +pub mod qbittorrent; \ No newline at end of file diff --git a/src/config/client/qbittorrent.rs b/src/config/client/qbittorrent.rs new file mode 100644 index 0000000..316765d --- /dev/null +++ b/src/config/client/qbittorrent.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct QBittorrentConfig { + pub url: String, + pub username: String, + pub password: String, +} \ No newline at end of file diff --git a/src/config/config.rs b/src/config/config.rs index c7dab7c..37ad462 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -9,7 +9,11 @@ use crate::torznab::TorznabClient; use super::CliProvider; -#[derive(Deserialize, Serialize)] +fn default_bool_true() -> bool { + true +} + +#[derive(Debug, Deserialize, Serialize)] pub struct Config { /// The path of the torrents to search. torrents_path: String, @@ -28,14 +32,13 @@ pub struct Config { /// Whether to cache using an external db (ie regis) or don't cache. #[serde(default)] pub use_cache: bool, - - /// Whether to keep the original torrent file and create a new one for cross-seed or delete original and upload cross-seed - #[serde(default)] - pub replace_torrents: bool, /// Whether or not to strip public trackers from cross-seed torrents. #[serde(default)] - pub strip_public: bool, + pub strip_public_trackers: bool, + + /// The category of added cross-seed torrents. + torrent_category: Option, /// Used for deserializing the indexers into a Vec. #[serde(rename = "indexers")] @@ -44,11 +47,16 @@ pub struct Config { /// The indexers to search. #[serde(skip)] pub indexers: Vec, + + /// Config section for qbittorrent client + pub qbittorrent: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] pub enum RunMode { + #[serde(alias = "script")] Script, + #[serde(alias = "daemon")] Daemon, } @@ -60,13 +68,25 @@ impl Default for RunMode { #[derive(Debug, Clone, Deserialize, Serialize)] pub enum TorrentMode { - Inject, - Search, + /// Inject a found torrent's trackers into the torrent being downloaded + /// by the client. + #[serde(alias = "inject_trackers", alias = "injecttrackers")] + InjectTrackers, + + /// Upload the torrent file to the torrent client. This will cause there + /// to be two uploading torrents on the client. One which is found by cross-seed + /// and the other which was imported by the user or another application. + #[serde(alias = "inject_file", alias = "injectfile")] + InjectFile, + + /// Cross-seeded torrents will be stored in the filesystem. + #[serde(alias = "search")] + Filesystem, } impl Default for TorrentMode { fn default() -> Self { - TorrentMode::Inject + TorrentMode::InjectTrackers } } @@ -145,4 +165,10 @@ impl Config { pub fn output_path_str(&self) -> Option<&String> { self.output_path.as_ref() } + + pub fn torrent_category(&self) -> String { + self.torrent_category.as_ref() + .unwrap_or(&String::from("cross-seed-rs")) + .clone() + } } \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs index cca2569..fad5816 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,8 +1,10 @@ pub mod config; -pub use config::Config; +pub use config::*; pub mod argument_tree; pub use argument_tree::*; pub mod cli_provider; -pub use cli_provider::*; \ No newline at end of file +pub use cli_provider::*; + +pub mod client; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index df17545..ce9d25d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,18 @@ mod config; mod torznab; +mod torrent_client; -use config::Config; +use config::{Config, TorrentMode}; + +use abstracttorrent::common::GetTorrentListParams; +use abstracttorrent::torrent::{TorrentUpload, TorrentState, TorrentInfo}; use lava_torrent::bencode::BencodeElem; -use tracing::{info, Level, debug}; +use tracing::{info, Level, debug, warn, error}; +use std::ops::Deref; use std::path::{Path, PathBuf}; use std::error::Error; +use std::vec; use lava_torrent::torrent::v1::{Torrent, AnnounceList}; @@ -39,15 +45,22 @@ fn read_torrents(path: &Path) -> Result, Box> { #[tokio::main] async fn main() { let subscriber = tracing_subscriber::fmt() - .with_max_level(Level::INFO) + .with_max_level(Level::DEBUG) .finish(); tracing::subscriber::set_global_default(subscriber).expect("Failed to set global default log subscriber"); // Get config and debug the torrents - let config = Config::new(); + let config = Arc::new(Config::new()); info!("Searching for torrents in: {}", config.torrents_path_str()); + // Get a torrent client from the config. + let mut torrent_client = torrent_client::TorrentClient::from_config(&config); + torrent_client.login(&config).await.unwrap(); + + // Torrent client no longer needs to mut, so we can just create an `Arc` without a mutex. + let torrent_client = Arc::new(torrent_client); + let mut indexers = config.indexers.clone(); // Create torznab clients for each indexer. @@ -64,24 +77,59 @@ async fn main() { // Log the amount of torrents. let torrent_files = read_torrents(config.torrents_path()).unwrap(); - info!("Found {} torrents", torrent_files.len()); + info!("Found {} torrent files...", torrent_files.len()); // Convert the indexers to be async friendly. let mut indexers = indexers.iter() .map(|indexer| Arc::new(RwLock::new(indexer.clone()))) .collect::>(); + // Store async tasks to wait for them to finish let mut indexer_handles = vec![]; - for torrent_path in torrent_files.iter() { - let torrent = Torrent::read_from_file(torrent_path).unwrap(); + info!("Parsing all torrent files..."); + + let mut stop = stopwatch::Stopwatch::start_new(); + // Get the torrents and from the paths + let mut torrents: Vec> = torrent_files.iter() + .map(|path| Torrent::read_from_file(path)) + .collect(); + stop.stop(); + info!("Took {} seconds to parse all torrents", stop.elapsed().as_secs()); + drop(stop); + + // Remove the torrents that failed to be read from the file, and + // are not in the download client. + + // NOTE: It might be better to get all torrents on the client and check that the torrents are on the + // client locally. + torrents.retain(|torrent| { + if let Ok(torrent) = torrent { + let info = futures::executor::block_on(torrent_client.get_torrent_info(&torrent)).unwrap(); + info.is_some() + } else { + false + } + }); + // Unwrap the results, all errored ones were removed from the `.retain` + let torrents: Vec = torrents.iter() + .map(|res| res.as_ref().unwrap().clone()) + .collect(); + + info!("Found {} torrents that are in the client and on the filesystem", torrents.len()); + + for torrent in torrents { let torrent = Arc::new(torrent); for indexer in indexers.iter() { info!("Checking for \"{}\"", torrent.name); + // Clone some `Arc`s for the new async task. let mut indexer = Arc::clone(indexer); let torrent = Arc::clone(&torrent); + let torrent_client = Arc::clone(&torrent_client); + let config = Arc::clone(&config); + indexer_handles.push(tokio::spawn(async move { let lock = indexer.read().await; match &lock.client { @@ -95,39 +143,159 @@ async fn main() { if let Some(result) = results.first() { let found_torrent = result.download_torrent().await.unwrap(); + // Check if we found the same torrent in its own indexer + if found_torrent.info_hash() == torrent.info_hash() { + debug!("Found same torrent in its own indexer, skipping..."); + return; + } + if let Some(found_announces) = &found_torrent.announce_list { // Some urls can be encoded so we need to decode to compare them. let found_announces: Vec> = found_announces.iter() - .map(|a_list| a_list.iter().map(|a| urlencoding::decode(a).unwrap().to_string()).collect::>()) + .map(|a_list| + a_list.iter().map(|a| urlencoding::decode(a) + .unwrap().to_string()) + .collect::>()) .collect(); - if let Some(torrent_announces) = &torrent.announce_list { - let mut found_announces_flat: Vec<&String> = Vec::new(); - for i in found_announces.iter() { - for j in i.iter() { - found_announces_flat.push(j); - } - } + // Get the trackers of the torrent from the download client. + let request_info = TorrentInfo::from_hash(torrent.info_hash()); + let torrent_announces = torrent_client.get_torrent_trackers(&request_info).await.unwrap(); + let torrent_announces: Vec<&String> = torrent_announces.iter().map(|t| &t.url).collect(); - let mut flat_announces: Vec<&String> = Vec::new(); - for i in torrent_announces.iter() { - for j in i.iter() { - flat_announces.push(j); - } - } + // Flatten the announce list to make them easier to search. + let found_announces: Vec<&String> = found_announces.iter() + .flat_map(|array| array.iter()) + .collect(); - // Check if the announce urls from the found torrent are in the one - // that is on the file system. - let mut in_tracker = true; - for found_url in found_announces_flat.iter() { - in_tracker = in_tracker && flat_announces.contains(found_url); - } + // Check if the client has the trackers of the torrent already. + let mut client_has_trackers = found_announces.iter() + .all(|tracker| torrent_announces.contains(tracker)); - if !in_tracker { - info!("Found a cross-seedable torrent for {}", found_torrent.name); - } else { - debug!("Found the torrent in its original indexer, skipping..."); + if !client_has_trackers { + info!("Found a cross-seedable torrent for {}", found_torrent.name); + + match torrent_client.get_torrent_info(&torrent).await.unwrap() { + Some(info) => { + info!("Got info: {:?}", info); + + match info.state { + TorrentState::Uploading | TorrentState::QueuedUploading => { + debug!("The torrent is being uploaded on the client"); + + //if config.add_trackers { + match config.torrent_mode { + TorrentMode::InjectTrackers => { + debug!("Can add trackers to the torrent"); + + if found_torrent.is_private() { + debug!("The found torrent is private, so we must remove the torrent and re-add it with the new trackers..."); + + match torrent_client.remove_torrent(&info, false).await { + Ok(()) => { + debug!("Re-uploading torrent to client..."); + + info!("Found announces: {:?}", found_announces); + + // Combine both announces and deref the Strings by cloning them. + let mut torrent_announces: Vec = torrent_announces.into_iter() + .chain(found_announces) + .cloned() + .collect(); + // Remove the [DHT], [PeX] and [LSD] announces from the list. + // The client should handle those. + torrent_announces.retain(|announce| !(announce.starts_with("** [") && announce.ends_with("] **"))); + + info!("Old torrent: {:?}", torrent.announce_list); + + let mut torrent = (*torrent).clone(); + torrent.announce_list = Some(vec![torrent_announces]); + if let Some(extra) = torrent.extra_info_fields.as_mut() { + extra.insert(String::from("private"), BencodeElem::Integer(1)); + } else { + let mut extra = std::collections::HashMap::new(); + extra.insert(String::from("private"), BencodeElem::Integer(1)); + torrent.extra_info_fields = Some(extra); + } + /* torrent.extra_info_fields.as_mut() + .unwrap_or(&mut std::collections::HashMap::new()) + .insert(String::from("private"), BencodeElem::Integer(1)); */ + + + info!("Torrent that will be uploaded: {:?}, private: {}", torrent.announce_list, torrent.is_private()); + + // Clone some fields from the torrent due to ownership issues with + // torrent.encode() + let name = torrent.name.clone(); + let hash = torrent.info_hash().clone(); + + match torrent.encode() { + Ok(bytes) => { + let upload = TorrentUpload::builder() + .category(config.torrent_category()) + .tags(info.tags) + .torrent_data(format!("{}.torrent", hash), bytes) + //.paused() + .build(); + + match torrent_client.add_torrent(&upload).await { + Ok(()) => info!("Added cross-seed torrent {}!", name), + Err(err) => error!("Error adding cross-seed torrent: {} (error: {:?}", name, err), + } + }, + Err(e) => error!("Error encoding torrent for upload: {}", e), + } + }, + Err(err) => error!("Error removing torrent from client: {} (error: {:?})", torrent.name, err), + } + } else { + debug!("Adding trackers to torrent since they aren't private..."); + let torrent_announces = torrent_announces.iter() + .map(|u| u.to_owned().to_owned()) + .collect(); + + if let Err(err) = torrent_client.add_torrent_trackers(&info, torrent_announces).await { + error!("Error adding torrent trackers to torrent: {} (err: {:?})", torrent.name, err); + } + } + }, + TorrentMode::InjectFile => { + debug!("Cannot add trackers, uploading new torrent..."); + + // Clone some fields from the torrent due to ownership issues with + // found_torrent.encode() + let name = found_torrent.name.clone(); + let hash = found_torrent.info_hash().clone(); + + match found_torrent.encode() { + Ok(bytes) => { + let upload = TorrentUpload::builder() + .torrent_data(format!("{}.torrent", hash), bytes) + .category(config.torrent_category()) + //.paused() // TODO: don't pause new uploads + .build(); + + match torrent_client.add_torrent(&upload).await { + Ok(()) => info!("Added cross-seed torrent {}!", name), + Err(err) => error!("Failure to add cross-seed torrent: {} (Error {:?})", name, err), + } + }, + Err(e) => warn!("Failure to encode ({}) {}", e, name), + } + }, + TorrentMode::Filesystem => { + todo!(); // TODO: implement + } + } + }, + _ => debug!("Torrent is not done downloading, skipping..."), + } + + }, + None => info!("Torrent file {} was not found in the client, skipping...", torrent.name), } + } else { + debug!("Found the torrent in its original indexer, skipping..."); } } } diff --git a/src/torrent_client/mod.rs b/src/torrent_client/mod.rs new file mode 100644 index 0000000..ec5dc1d --- /dev/null +++ b/src/torrent_client/mod.rs @@ -0,0 +1,58 @@ +use std::ops::{Deref, DerefMut}; + +use abstracttorrent::{client::qbittorrent, torrent::TorrentInfo, common::GetTorrentListParams}; + +use crate::config::Config; + +pub struct TorrentClient { + client: Box, +} + +impl TorrentClient { + pub fn from_config(config: &Config) -> Self { + // TODO: figure out which client to use if multiple are specified. + + if let Some(qbittorrent) = &config.qbittorrent { + TorrentClient { + client: Box::new(qbittorrent::client::QBittorrentClient::new()) + } + } else { + panic!("Invalid config!"); + } + } + + pub async fn login(&mut self, config: &Config) -> abstracttorrent::client::ClientResult<()> { + let (url, username, password) = match &config.qbittorrent { + Some(qb) => { + (&qb.url, &qb.username, &qb.password) + }, + None => { + panic!("Invalid config!"); + } + }; + + self.client.login(&url, username, password).await + } + + /* pub fn login_with_config(&self, config: &Config) -> abstracttorrent::client::ClientResult<()> { + self.login(url, username, password) + } */ + + pub async fn get_torrent_info(&self, torrent: &lava_torrent::torrent::v1::Torrent) -> abstracttorrent::client::ClientResult> { + let params = GetTorrentListParams::builder() + .hash(&torrent.info_hash()) + .build(); + + let results = self.client.get_torrent_list(Some(params)).await?; + Ok(results.first().cloned()) + } +} + +impl Deref for TorrentClient { + type Target = Box; + fn deref(&self) -> &Self::Target { &self.client } +} + +impl DerefMut for TorrentClient { + fn deref_mut(&mut self) -> &mut Box { &mut self.client } +} \ No newline at end of file