From 11a333bef44c85e660feb73c511b8eace399d61f Mon Sep 17 00:00:00 2001 From: Xe Date: Thu, 21 Oct 2021 12:27:47 -0400 Subject: [PATCH] initial commit Signed-off-by: Xe --- .envrc | 1 + .gitignore | 2 + Cargo.lock | 1016 ++++++++++++++++++ Cargo.toml | 2 + LICENSE | 12 + crates/lena/.gitignore | 1 + crates/lena/Cargo.toml | 8 + crates/lena/src/main.rs | 3 + crates/transmission-rs/.gitignore | 5 + crates/transmission-rs/.gitlab-ci.yml | 27 + crates/transmission-rs/Cargo.toml | 19 + crates/transmission-rs/LICENSE | 21 + crates/transmission-rs/README.md | 5 + crates/transmission-rs/alpine.torrent | Bin 0 -> 29782 bytes crates/transmission-rs/src/client.rs | 179 +++ crates/transmission-rs/src/client/list.rs | 23 + crates/transmission-rs/src/client/payload.rs | 27 + crates/transmission-rs/src/client/torrent.rs | 84 ++ crates/transmission-rs/src/error.rs | 16 + crates/transmission-rs/src/lib.rs | 18 + shell.nix | 11 + 21 files changed, 1480 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 crates/lena/.gitignore create mode 100644 crates/lena/Cargo.toml create mode 100644 crates/lena/src/main.rs create mode 100644 crates/transmission-rs/.gitignore create mode 100644 crates/transmission-rs/.gitlab-ci.yml create mode 100644 crates/transmission-rs/Cargo.toml create mode 100644 crates/transmission-rs/LICENSE create mode 100644 crates/transmission-rs/README.md create mode 100644 crates/transmission-rs/alpine.torrent create mode 100644 crates/transmission-rs/src/client.rs create mode 100644 crates/transmission-rs/src/client/list.rs create mode 100644 crates/transmission-rs/src/client/payload.rs create mode 100644 crates/transmission-rs/src/client/torrent.rs create mode 100644 crates/transmission-rs/src/error.rs create mode 100644 crates/transmission-rs/src/lib.rs create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..051d09d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e92f4cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +result* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0e5380f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1016 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "async-compression" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443ccbb270374a2b1055fc72da40e1f237809cd6bb0e97e66d264cd138473a6" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +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 = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-sink" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" + +[[package]] +name = "futures-task" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" + +[[package]] +name = "futures-util" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +dependencies = [ + "autocfg", + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + +[[package]] +name = "hyper" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lena" +version = "0.1.0" + +[[package]] +name = "libc" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "openssl" +version = "0.10.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" + +[[package]] +name = "ppv-lite86" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" + +[[package]] +name = "proc-macro2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" +dependencies = [ + "async-compression", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-util", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + +[[package]] +name = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "pin-project-lite", + "winapi", +] + +[[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", +] + +[[package]] +name = "tokio-util" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "transmission_rs" +version = "0.5.0" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8b7cd6a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = [ "crates/*" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4cb9bcc --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ + YOLO LICENSE + Version 1, July 10 2015 + +THIS SOFTWARE LICENSE IS PROVIDED "ALL CAPS" SO THAT YOU KNOW IT IS SUPER +SERIOUS AND YOU DON'T MESS AROUND WITH COPYRIGHT LAW BECAUSE YOU WILL GET IN +TROUBLE HERE ARE SOME OTHER BUZZWORDS COMMONLY IN THESE THINGS WARRANTIES +LIABILITY CONTRACT TORT LIABLE CLAIMS RESTRICTION MERCHANTABILITY SUBJECT TO +THE FOLLOWING CONDITIONS: + +1. #yolo +2. #swag +3. #blazeit diff --git a/crates/lena/.gitignore b/crates/lena/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/lena/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/lena/Cargo.toml b/crates/lena/Cargo.toml new file mode 100644 index 0000000..a3559d5 --- /dev/null +++ b/crates/lena/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lena" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/lena/src/main.rs b/crates/lena/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/crates/lena/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/crates/transmission-rs/.gitignore b/crates/transmission-rs/.gitignore new file mode 100644 index 0000000..9c37f73 --- /dev/null +++ b/crates/transmission-rs/.gitignore @@ -0,0 +1,5 @@ +/target +**/*.rs.bk +Cargo.lock + +.idea \ No newline at end of file diff --git a/crates/transmission-rs/.gitlab-ci.yml b/crates/transmission-rs/.gitlab-ci.yml new file mode 100644 index 0000000..aa4b888 --- /dev/null +++ b/crates/transmission-rs/.gitlab-ci.yml @@ -0,0 +1,27 @@ +stages: + - test + - deploy-dryrun + - deploy + +Build and test Rust code: + stage: test + image: rustlang/rust:nightly + script: + - cargo test + +Test publishing crate: + stage: deploy-dryrun + image: rustlang/rust:nightly + script: + - cargo publish --dry-run + only: + - tags + +Publish crate: + stage: deploy + image: rustlang/rust:nightly + script: + - sh -c 'cargo login "${CARGO_TOKEN}" && cargo publish' + only: + - tags + diff --git a/crates/transmission-rs/Cargo.toml b/crates/transmission-rs/Cargo.toml new file mode 100644 index 0000000..3524c2a --- /dev/null +++ b/crates/transmission-rs/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transmission_rs" +version = "0.5.0" +authors = ["Mike Cronce "] +edition = "2018" +readme = "README.md" +license = "MIT" +repository = "https://gitlab.cronce.io/foss/transmission-rs" +categories = ["api-bindings"] +keywords = ["torrent", "bittorrent", "transmission"] +description = "A safe, ergonomic, async client for the Transmission BitTorrent client implemented in pure Rust" + +[dependencies] +reqwest = {version = "0.11", features = ["gzip", "json"]} +serde = {version = "1", features = ["derive"]} +serde_json = "1" +thiserror = "1" +tokio = "1" + diff --git a/crates/transmission-rs/LICENSE b/crates/transmission-rs/LICENSE new file mode 100644 index 0000000..1a65f6d --- /dev/null +++ b/crates/transmission-rs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Steven vanZyl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/transmission-rs/README.md b/crates/transmission-rs/README.md new file mode 100644 index 0000000..c7b2c59 --- /dev/null +++ b/crates/transmission-rs/README.md @@ -0,0 +1,5 @@ +# transmission-rs +Ergonomic Rust bindings for the [Transmission](https://transmissionbt.com/) BitTorrent client implemented in pure Rust. + +Documentation coming Soon(TM) + diff --git a/crates/transmission-rs/alpine.torrent b/crates/transmission-rs/alpine.torrent new file mode 100644 index 0000000000000000000000000000000000000000..eec9ebfe9feed57c91df8b3972cff0ab24625090 GIT binary patch literal 29782 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, + + base_url: String, + session_id: Option, + + http: reqwest::Client, +} + +impl Client { + // TODO: Take Option<(&str, &str)> for auth + pub fn new(host: impl ToString, port: u16, tls: bool, auth: Option<(String, String)>) -> Result { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert(reqwest::header::USER_AGENT, "transmission-rs/0.1".try_into()?); + let this = Self{ + host: host.to_string(), + port: port, + tls: tls, + auth: auth, + base_url: { + let protocol = match tls { + true => "https", + false => "http" + }; + format!("{}://{}:{}", protocol, host.to_string(), port) + }, + session_id: None, + http: reqwest::Client::builder() + .gzip(true) + .default_headers(headers) + .build()? + }; + Ok(this) + } + + fn build_url(&self, path: impl AsRef) -> String { + format!("{}{}", self.base_url, path.as_ref()).to_string() + } + + pub async fn authenticate(&mut self) -> Result<(), Error> { + self.session_id = None; + let response = self.post("/transmission/rpc/") + .header("Content-Type", "application/x-www-form-urlencoded") + .send() + .await?; + self.session_id = match response.headers().get("X-Transmission-Session-Id") { + Some(v) => Some(v.to_str()?.to_string()), + None => None // TODO: Return an error + }; + Ok(()) + } + + pub fn post(&self, path: impl AsRef) -> reqwest::RequestBuilder { + let url = self.build_url(path); + //println!("{}", url); + let mut request = self.http.post(&url); + request = match &self.auth { + Some(auth) => request.basic_auth(&auth.0, Some(&auth.1)), + None => request + }; + request = match &self.session_id { + Some(token) => request.header("X-Transmission-Session-Id", token), + None => request + }; + request + } + + pub async fn rpc(&mut self, method: impl ToString, tag: u8, body: impl Serialize) -> Result { + let request = Request::new(method, tag, body); + let mut error = None; + for i in 0..5 { + sleep(Duration::from_secs(i)).await; + let result = self.post("/transmission/rpc/") + // Content-Type doesn't actually appear to be necessary, and is + // technically a lie, since there's no key/value being passed, + // just some JSON, but the official client sends this, so we + // will too. + .header("Content-Type", "application/x-www-form-urlencoded") + .body(serde_json::to_string(&request)?) + .send().await? + .error_for_status(); + match result { + Ok(r) => return Ok(r), + Err(e) => match e.status() { + Some(reqwest::StatusCode::CONFLICT) => { + self.authenticate().await?; + error = Some(e); + continue; + }, + _ => return Err(e.into()) + } + }; + } + match error { + Some(e) => Err(e.into()), + // Should be unreachable + None => Err(Error::HTTPUnknown) + } + } + + pub async fn list(&mut self) -> Result, Error> { + let field_list = FieldList::from_vec(vec![ + "error", + "errorString", + "eta", + "id", + "isFinished", + "leftUntilDone", + "name", + "peersGettingFromUs", + "peersSendingToUs", + "rateDownload", + "rateUpload", + "sizeWhenDone", + "status", + "uploadRatio" + ]); + let response: Response = self.rpc("torrent-get", 4, field_list).await?.error_for_status()?.json().await?; + Ok(response.arguments.torrents) + } + + // TODO: Borrow key from value + pub async fn list_by_name(&mut self) -> Result, Error> { + Ok(self.list().await?.into_iter().map(|v| (v.name.clone(), v)).collect::>()) + } + + pub async fn add_torrent_from_link(&mut self, url: impl ToString) -> Result { + let response: Response = self.rpc("torrent-add", 8, AddTorrent{filename: url.to_string()}).await?.error_for_status()?.json().await?; + Ok(response.arguments) + } + + pub async fn get_files_by_id(&mut self, id: u16) -> Result, Error> { + let response: Response = self.rpc("torrent-get", 3, GetTorrentFilesRequest::new(id)).await?.error_for_status()?.json().await?; + if(response.arguments.torrents.len() == 0) { + // TODO: Maybe make the Ok result an Option? Or maybe return a 404 error? + return Ok(vec![]); + } else if(response.arguments.torrents.len() > 1) { + return Err(Error::WeirdGetFilesResponse); + } + // TODO: Figure out how to move files out of this structure, since we're discarding the rest anyway, instead of cloning + Ok(response.arguments.torrents[0].files.clone()) + } +} + diff --git a/crates/transmission-rs/src/client/list.rs b/crates/transmission-rs/src/client/list.rs new file mode 100644 index 0000000..bc300b7 --- /dev/null +++ b/crates/transmission-rs/src/client/list.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; +use serde::Serialize; + +use super::torrent::Torrent; + +#[derive(Debug, Deserialize, Serialize)] +pub struct TorrentList { + pub torrents: Vec +} + +#[derive(Debug, Serialize)] +pub struct FieldList { + pub fields: Vec +} + +impl FieldList { + pub fn from_vec(vec: Vec) -> Self { + Self{ + fields: vec.iter().map(|s| s.to_string()).collect() + } + } +} + diff --git a/crates/transmission-rs/src/client/payload.rs b/crates/transmission-rs/src/client/payload.rs new file mode 100644 index 0000000..dcee55a --- /dev/null +++ b/crates/transmission-rs/src/client/payload.rs @@ -0,0 +1,27 @@ +use serde::Deserialize; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct Request { + method: String, + tag: u8, + pub arguments: T +} + +impl Request { + pub fn new(method: impl ToString, tag: u8, arguments: T) -> Self { + Self{ + method: method.to_string(), + tag: tag, + arguments: arguments + } + } +} + +#[derive(Debug, Deserialize)] +pub struct Response { + result: String, + tag: u8, + pub arguments: T +} + diff --git a/crates/transmission-rs/src/client/torrent.rs b/crates/transmission-rs/src/client/torrent.rs new file mode 100644 index 0000000..a49f33e --- /dev/null +++ b/crates/transmission-rs/src/client/torrent.rs @@ -0,0 +1,84 @@ +use std::path::PathBuf; + +use serde::Deserialize; +use serde::Serialize; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Torrent { + pub id: u16, + pub error: u8, + pub error_string: String, + pub eta: i64, // TODO: Option with -1 as None + pub is_finished: bool, + pub left_until_done: u64, // TODO: u32? + pub name: String, + pub peers_getting_from_us: u16, + pub peers_sending_to_us: u16, + pub rate_download: u32, + pub rate_upload: u32, + pub size_when_done: u64, + pub status: u8, + pub upload_ratio: f32 +} + +#[derive(Debug, Serialize)] +pub struct AddTorrent { + pub filename: String +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AddedTorrent { + pub id: u16, + pub name: String, + pub hash_string: String +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum AddTorrentResponse { + TorrentAdded(AddedTorrent), + TorrentDuplicate(AddedTorrent) +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct GetTorrentFilesRequest { + fields: Vec, + ids: u16 +} + +impl GetTorrentFilesRequest { + pub fn new(id: u16) -> Self { + Self{ + fields: vec!["files".to_string()], + ids: id + } + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TorrentFile { + pub name: PathBuf, + pub bytes_completed: u64, + pub length: u64 +} + +impl TorrentFile { + pub fn take_name(self) -> PathBuf { + self.name + } +} + +#[derive(Debug, Deserialize)] +pub struct TorrentFileWrapper { + pub files: Vec +} + +#[derive(Debug, Deserialize)] +pub struct GetTorrentFilesResponse { + pub torrents: Vec +} + diff --git a/crates/transmission-rs/src/error.rs b/crates/transmission-rs/src/error.rs new file mode 100644 index 0000000..1ac0414 --- /dev/null +++ b/crates/transmission-rs/src/error.rs @@ -0,0 +1,16 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("HTTP error")] + Reqwest(#[from] reqwest::Error), + #[error("HTTP header decoding error")] + ReqwestHeaderDecode(#[from] reqwest::header::ToStrError), + #[error("invalid HTTP header value")] + ReqwestHeaderValue(#[from] reqwest::header::InvalidHeaderValue), + #[error("Unknown HTTP failure")] + HTTPUnknown, + #[error("JSON error")] + JSON(#[from] serde_json::Error), + #[error("got more than one 'torrents' entry in a get files response")] + WeirdGetFilesResponse, +} + diff --git a/crates/transmission-rs/src/lib.rs b/crates/transmission-rs/src/lib.rs new file mode 100644 index 0000000..d5d066c --- /dev/null +++ b/crates/transmission-rs/src/lib.rs @@ -0,0 +1,18 @@ +#![allow(unused_parens)] +//! Ergonomic Rust bindings for the [Transmission](https://transmissionbt.com/) BitTorrent client +//! based on [transmission-sys](https://gitlab.com/tornado-torrent/transmission-sys). +//! +//! Most interaction will be done through the `Client` struct. + +extern crate serde; +extern crate serde_json; + +// Re-exports +mod client; +pub use client::Client; +pub use client::torrent::Torrent; +pub use client::torrent::AddTorrentResponse; +pub use client::torrent::TorrentFile; +mod error; +pub use error::Error; + diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..5859c41 --- /dev/null +++ b/shell.nix @@ -0,0 +1,11 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + rustc cargo rustfmt rls rust-analyzer + pkg-config sqlite openssl + + # keep this line if you use bash + pkgs.bashInteractive + ]; +}