transmission-gobject-0.1.7/.cargo_vcs_info.json0000644000000001360000000000100152050ustar { "git": { "sha1": "f96b19ce3a56925ff1f5486128bb68f81e1088a9" }, "path_in_vcs": "" }transmission-gobject-0.1.7/.gitignore000064400000000000000000000000231046102023000157600ustar 00000000000000/target Cargo.lock transmission-gobject-0.1.7/Cargo.lock0000644000001571010000000000100131650ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "async-channel" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-compat" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0" dependencies = [ "futures-core", "futures-io", "once_cell", "pin-project-lite", "tokio", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "shlex", ] [[package]] name = "cfg-expr" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d0390889d58f934f01cd49736275b4c2da15bcfc328c78ff2349907e6cabf22" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", "windows-link", ] [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.60.2", ] [[package]] name = "event-listener" version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "273d64c833fbbf7cd86c4cdced893c5d3f2f5d6aeb30fd0c30d172456ce8be2e" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", "glib", "libc", "pin-project-lite", "smallvec", ] [[package]] name = "gio-sys" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8130f5810a839d74afc3a929c34a700bf194972bb034f2ecfe639682dd13cc" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", "windows-sys 0.60.2", ] [[package]] name = "glib" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "690e8bcf8a819b5911d6ae79879226191d01253a4f602748072603defd5b9553" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", "memchr", "smallvec", ] [[package]] name = "glib-macros" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e772291ebea14c28eb11bb75741f62f4a4894f25e60ce80100797b6b010ef0f9" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "glib-sys" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2be4c74454fb4a6bd3328320737d0fa3d6939e2d570f5d846da00cb222f6a0" dependencies = [ "libc", "system-deps", ] [[package]] name = "gobject-sys" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab318a786f9abd49d388013b9161fa0ef8218ea6118ee7111c95e62186f7d31f" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "h2" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap 2.10.0", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", "system-configuration", "tokio", "tower-service", "tracing", "windows-registry", ] [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", "serde", ] [[package]] name = "indexmap" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", "serde", ] [[package]] name = "io-uring" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_enum" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", "rustversion", ] [[package]] name = "num_enum_derive" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[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.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "ref-cast" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "reqwest" version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", "mime", "native-tls", "percent-encoding", "pin-project-lite", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.60.2", ] [[package]] name = "rustls" version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "schemars" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ "dyn-clone", "ref-cast", "serde", "serde_json", ] [[package]] name = "schemars" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", "serde", "serde_json", ] [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_path_to_error" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", ] [[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_with" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.10.0", "schemars 0.9.0", "schemars 1.0.4", "serde", "serde_derive", "serde_json", "serde_with_macros", "time", ] [[package]] name = "serde_with_macros" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "system-deps" version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "tempfile" version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "slab", "socket2", "windows-sys 0.59.0", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "transmission-client" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6201c18962d781bd38e9aef8f4f298a1f1d9343a4df9c0cb6e7b50bc6a2a0bc" dependencies = [ "log", "reqwest", "serde", "serde_derive", "serde_json", "serde_path_to_error", "serde_with", "thiserror", "url", ] [[package]] name = "transmission-gobject" version = "0.1.7" dependencies = [ "async-channel", "async-compat", "gio", "glib", "indexmap 2.10.0", "log", "num_enum", "once_cell", "transmission-client", "url", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.3", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn", ] transmission-gobject-0.1.7/Cargo.toml0000644000000027070000000000100132110ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "transmission-gobject" version = "0.1.7" authors = ["Felix Häcker "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "gtk-rs gobject wrapper for transmission-client crate" homepage = "https://gitlab.gnome.org/haecker-felix/transmission-gobject" documentation = "https://docs.rs/transmission-gobject" readme = false license = "MIT" repository = "https://gitlab.gnome.org/haecker-felix/transmission-gobject" [lib] name = "transmission_gobject" path = "src/lib.rs" [dependencies.async-channel] version = "2.5" [dependencies.async-compat] version = "0.2" [dependencies.gio] version = "0.21" [dependencies.glib] version = "0.21" [dependencies.indexmap] version = "2.10" [dependencies.log] version = "0.4" [dependencies.num_enum] version = "0.7" [dependencies.once_cell] version = "1.21" [dependencies.transmission-client] version = "0.1.1" [dependencies.url] version = "2.5" transmission-gobject-0.1.7/Cargo.toml.orig000064400000000000000000000011441046102023000166640ustar 00000000000000[package] name = "transmission-gobject" version = "0.1.7" authors = [ "Felix Häcker " ] edition = "2024" description = "gtk-rs gobject wrapper for transmission-client crate" license = "MIT" documentation = "https://docs.rs/transmission-gobject" homepage = "https://gitlab.gnome.org/haecker-felix/transmission-gobject" repository = "https://gitlab.gnome.org/haecker-felix/transmission-gobject" [dependencies] async-channel = "2.5" async-compat = "0.2" gio = "0.21" glib = "0.21" indexmap = "2.10" log = "0.4" num_enum = "0.7" once_cell = "1.21" transmission-client = "0.1.1" url = "2.5" transmission-gobject-0.1.7/src/authentication.rs000064400000000000000000000021061046102023000201500ustar 00000000000000use std::cell::OnceCell; use glib::Properties; use glib::object::ObjectExt; use glib::subclass::prelude::{ObjectSubclass, *}; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrAuthentication)] pub struct TrAuthentication { #[property(get, set, construct_only)] pub username: OnceCell, #[property(get, set, construct_only)] pub password: OnceCell, } #[glib::object_subclass] impl ObjectSubclass for TrAuthentication { const NAME: &'static str = "TrAuthentication"; type Type = super::TrAuthentication; type ParentType = glib::Object; } #[glib::derived_properties] impl ObjectImpl for TrAuthentication {} } glib::wrapper! { pub struct TrAuthentication(ObjectSubclass); } impl TrAuthentication { pub fn new(username: &str, password: &str) -> Self { glib::Object::builder() .property("username", username) .property("password", password) .build() } } transmission-gobject-0.1.7/src/client.rs000064400000000000000000000521121046102023000164110ustar 00000000000000use std::cell::{Cell, OnceCell, RefCell}; use std::collections::BTreeMap; use std::time::Duration; use async_channel::{Receiver, Sender}; use async_compat::CompatExt; use gio::prelude::*; use glib::subclass::Signal; use glib::subclass::prelude::{ObjectSubclass, *}; use glib::{Properties, SourceId, clone}; use once_cell::sync::Lazy; use transmission_client::{ Authentication, Client, ClientError, Torrent, TorrentFiles, TorrentPeers, }; use url::Url; use crate::{TrAuthentication, TrSession, TrSessionStats, TrTorrent, TrTorrentModel}; mod imp { use super::*; #[derive(Properties)] #[properties(wrapper_type = super::TrClient)] pub struct TrClient { #[property(get)] address: RefCell, #[property(get=Self::polling_rate, set=Self::set_polling_rate)] polling_rate: Cell, #[property(get)] torrents: TrTorrentModel, #[property(get)] session: OnceCell, #[property(get)] session_stats: TrSessionStats, #[property(get = Self::is_busy)] is_busy: std::marker::PhantomData, #[property(get = Self::is_connected)] is_connected: std::marker::PhantomData, pub client: RefCell>, pub polling_source_id: RefCell>, pub authentication: RefCell>, pub do_connect: Cell, pub do_disconnect: Cell, pub sender: Sender, pub receiver: Receiver, } impl TrClient { fn is_busy(&self) -> bool { self.do_connect.get() || self.do_disconnect.get() } fn is_connected(&self) -> bool { self.client.borrow().is_some() } } #[glib::object_subclass] impl ObjectSubclass for TrClient { const NAME: &'static str = "TrClient"; type ParentType = glib::Object; type Type = super::TrClient; fn new() -> Self { let (sender, receiver) = async_channel::bounded(1); Self { address: RefCell::default(), polling_rate: Cell::new(1000), torrents: TrTorrentModel::default(), session: OnceCell::default(), session_stats: TrSessionStats::default(), is_busy: Default::default(), is_connected: Default::default(), client: RefCell::default(), polling_source_id: RefCell::default(), authentication: RefCell::default(), do_connect: Cell::default(), do_disconnect: Cell::default(), sender, receiver, } } } #[glib::derived_properties] impl ObjectImpl for TrClient { fn constructed(&self) { self.parent_constructed(); let session = TrSession::new(&self.obj()); self.session.set(session).unwrap(); } fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![ Signal::builder("connection-failure").build(), Signal::builder("torrent-added") .param_types([TrTorrent::static_type()]) .build(), Signal::builder("torrent-downloaded") .param_types([TrTorrent::static_type()]) .build(), ] }); SIGNALS.as_ref() } } impl TrClient { pub async fn connect_internal(&self, address: String) -> Result<(), ClientError> { if self.is_connected() { self.obj().disconnect(false).await; } debug!("Connect to {address} ..."); // Check if the address has changed, and delete cached auth information if // needed if address != self.obj().address() { *self.authentication.borrow_mut() = None; } // Create new client for the rpc communication let url = Url::parse(&address).unwrap(); let client = Client::new(url); // Authenticate if we have information... if self.authentication.borrow().is_some() { self.rpc_auth(Some(client.clone())); } // Set properties self.address.borrow_mut().clone_from(&address); self.obj().notify_address(); // Check server version let session = client.session().compat().await?; debug!( "Connected to transmission session version {}", session.version ); // Make first poll *self.client.borrow_mut() = Some(client.clone()); self.poll_data(true).await?; self.obj().notify_is_connected(); // Start polling self.start_polling(); Ok(()) } pub fn start_polling(&self) { if let Some(id) = self.polling_source_id.borrow_mut().take() { warn!("Polling source id wasn't None"); id.remove(); } let duration = Duration::from_millis(self.polling_rate.get()); let id = glib::source::timeout_add_local( duration, clone!( #[weak(rename_to = this)] self, #[upgrade_or_panic] move || { let disconnect = this.do_disconnect.get(); if disconnect { debug!("Stop polling loop..."); // Send message back to the disconnect method to indicate that the // polling loop has been stopped let sender = this.sender.clone(); sender.send_blocking(true).unwrap(); this.do_disconnect.set(false); this.obj().notify_is_busy(); } else { let fut = clone!( #[weak] this, async move { debug!("Poll... ({}ms)", this.polling_rate.get()); this.obj().refresh_data().await; } ); glib::spawn_future_local(fut); } if disconnect { *this.polling_source_id.borrow_mut() = None; glib::ControlFlow::Break } else { glib::ControlFlow::Continue } } ), ); *self.polling_source_id.borrow_mut() = Some(id); } pub fn rpc_auth(&self, rpc_client: Option) { if let Some(auth) = &*self.authentication.borrow() { if let Some(rpc_client) = rpc_client { let rpc_auth = Authentication { username: auth.username(), password: auth.password(), }; rpc_client.set_authentication(Some(rpc_auth)); } else { warn!("Unable to authenticate, no rpc connection"); } } else { warn!("Unable to authenticate, no information stored"); } } pub async fn poll_data(&self, is_initial_poll: bool) -> Result<(), ClientError> { if let Some(client) = self.obj().rpc_client() { let torrents = client.torrents(None).compat().await?; // Transmission assigns each torrent a "global" queue position, which means // *every* torrent has a queue position, ignoring the torrent status. Therefore // that position doesn't match the actual position of the download / seed queue, // so we determine the position and set it as as property of TrTorrent. let download_queue = Self::sorted_queue_by_status(&torrents, 3); // 3 -> DownloadWait let seed_queue = Self::sorted_queue_by_status(&torrents, 5); // 5 -> SeedWait // We have to find out which torrent isn't included anymore, // so we can remove it from the model too. let mut hashes_delta = self.torrents.get_hashes(); // Count downloaded torrents so we can use that value for TrSessionStats let mut downloaded_torrents = 0; for rpc_torrent in torrents { let hash = rpc_torrent.hash_string.clone(); let download_queue_pos = Self::queue_pos(&download_queue, &rpc_torrent); let seed_queue_pos = Self::queue_pos(&seed_queue, &rpc_torrent); // Increase downloaded torrents count if it's SeedWait(5) or Seed(6) if rpc_torrent.status == 5 || rpc_torrent.status == 6 { downloaded_torrents += 1; } if let Some(torrent) = self.torrents.torrent_by_hash(hash.clone()) { let id = Some(vec![rpc_torrent.id]); // Update the download / seed queue position torrent.refresh_queue_positions(download_queue_pos, seed_queue_pos); // Only retrieve and deserialize files/peers information // on demand to save resources. Often those information aren't needed. let (torrent_files, torrent_peers) = if torrent.update_extra_info() { ( client .torrents_files(id.clone()) .compat() .await? .first() .cloned() .unwrap_or_default(), client .torrents_peers(id) .compat() .await? .first() .cloned() .unwrap_or_default(), ) } else { (TorrentFiles::default(), TorrentPeers::default()) }; // Check if torrent status or "global" queue position changed if torrent.status() as u32 != rpc_torrent.status as u32 || torrent.queue_position() != rpc_torrent.queue_position { // Update torrent properties torrent.refresh_values(&rpc_torrent, &torrent_files, &torrent_peers); // ... yes -> emit torrent model `status-changed` signal self.torrents.emit_by_name::<()>("status-changed", &[]); } else { // Update torrent properties, without emitting the status-changed signal torrent.refresh_values(&rpc_torrent, &torrent_files, &torrent_peers); } let index = hashes_delta.iter().position(|x| *x == hash).unwrap(); hashes_delta.remove(index); } else { debug!("Add new torrent: {}", rpc_torrent.name); let torrent = TrTorrent::from_rpc_torrent(&rpc_torrent, &self.obj()); torrent.refresh_queue_positions(download_queue_pos, seed_queue_pos); // Set dynamic values torrent.refresh_values( &rpc_torrent, &TorrentFiles::default(), &TorrentPeers::default(), ); self.torrents.add_torrent(&torrent); if !is_initial_poll { self.obj().emit_by_name::<()>("torrent-added", &[&torrent]); } } } // Remove the deltas from the model for hash in hashes_delta { let torrent = self.torrents.torrent_by_hash(hash).unwrap(); self.torrents.remove_torrent(&torrent); } let rpc_session = client.session().compat().await?; self.obj().session().refresh_values(rpc_session); let rpc_session_stats = client.session_stats().compat().await?; self.obj() .session_stats() .refresh_values(rpc_session_stats, downloaded_torrents); } else { warn!("Unable to poll transmission data, no rpc connection."); } Ok(()) } pub fn sorted_queue_by_status(rpc_torrents: &[Torrent], status_code: i32) -> Vec { let mut download_queue: BTreeMap = BTreeMap::new(); // Get all torrents with given status code for torrent in rpc_torrents { if torrent.status == status_code { download_queue.insert(torrent.queue_position.try_into().unwrap(), torrent); } } // Insert the sorted torrents into a vec let mut result = Vec::new(); for torrent in download_queue { result.push(torrent.1.clone()); } result } pub fn queue_pos(queue: &[Torrent], rpc_torrent: &Torrent) -> i32 { queue .iter() .position(|t| t == rpc_torrent) .map(|pos| pos as i32) .unwrap_or(-1) } pub fn polling_rate(&self) -> u64 { self.polling_rate.get() } pub fn set_polling_rate(&self, ms: u64) { self.polling_rate.set(ms); let id = self.polling_source_id.borrow_mut().take(); if let Some(id) = id { id.remove(); self.start_polling(); } } } } glib::wrapper! { pub struct TrClient(ObjectSubclass); } impl Default for TrClient { fn default() -> Self { Self::new() } } impl TrClient { pub fn new() -> Self { glib::Object::new() } pub async fn test_connectivity( address: String, auth: Option, ) -> Result<(), ClientError> { let url = Url::parse(&address).unwrap(); let rpc_client = Client::new(url); if let Some(auth) = auth { let rpc_auth = Authentication { username: auth.username(), password: auth.password(), }; rpc_client.set_authentication(Some(rpc_auth)); } let session = rpc_client.session().compat().await?; debug!( "Connectivity test to {} succeeded: Transmission daemon version {}", address, session.version ); Ok(()) } pub async fn test_port(&self) -> Result { if let Some(rpc_client) = self.rpc_client() { rpc_client.port_test().compat().await } else { warn!("Unable to test port, no rpc connection"); Ok(false) } } pub async fn connect(&self, address: String) -> Result<(), ClientError> { let imp = self.imp(); if self.is_busy() { warn!("Client is currently busy, unable to connect to new address."); return Ok(()); } // With the do_connect/disconnect variables we avoid race conditions // eg. doing another connect attempt, while the client is already busy imp.do_connect.set(true); self.notify_is_busy(); // Do the actual connecting work let result = imp.connect_internal(address).await; // Work is done -> unblock it again. imp.do_connect.set(false); self.notify_is_busy(); result } pub async fn disconnect(&self, close_session: bool) { let imp = self.imp(); if !self.is_connected() { warn!("Unable to disconnect, is not connected."); return; } if close_session { let client = imp.client.borrow().as_ref().unwrap().clone(); client.session_close().compat().await.unwrap(); } let _client = imp.client.borrow_mut().take().unwrap(); self.notify_is_connected(); // Wait till polling loop has stopped imp.do_disconnect.set(true); self.notify_is_busy(); // Wait from message from polling loop to ensure we're not polling anymore. let r = imp.receiver.recv().await.unwrap(); debug!("Stopped polling: {r:?}"); // Not connected anymore -> clear torrents model self.torrents().clear(); debug!("Disconnected from transmission session"); } pub fn set_authentication(&self, auth: TrAuthentication) { let imp = self.imp(); *imp.authentication.borrow_mut() = Some(auth); imp.rpc_auth(self.rpc_client()); } /// `filename` can be a magnet url or a local file path pub async fn add_torrent_by_filename(&self, filename: String) -> Result<(), ClientError> { if let Some(client) = self.rpc_client() { client.torrent_add_filename(&filename).compat().await?; self.refresh_data().await; } else { warn!("Unable to add new torrent, no rpc connection"); } Ok(()) } /// `metainfo` is the base64 encoded content of a .torrent file pub async fn add_torrent_by_metainfo(&self, metainfo: String) -> Result<(), ClientError> { if let Some(client) = self.rpc_client() { client.torrent_add_metainfo(&metainfo).compat().await?; self.refresh_data().await; } else { warn!("Unable to add new torrent, no rpc connection"); } Ok(()) } pub async fn remove_torrents( &self, only_downloaded: bool, delete_local_data: bool, ) -> Result<(), ClientError> { let model = self.torrents(); let mut remove_hashes = vec![]; for i in 0..model.n_items() { let torrent = model.item(i).unwrap().downcast::().unwrap(); if !only_downloaded || (torrent.size() == torrent.downloaded() && torrent.downloaded() != 0) { remove_hashes.push(torrent.hash()); } } if let Some(client) = self.rpc_client() { client .torrent_remove(Some(remove_hashes), delete_local_data) .compat() .await?; self.refresh_data().await; } else { warn!("Unable to remove torrents, no rpc connection"); } Ok(()) } pub async fn start_torrents(&self) -> Result<(), ClientError> { let model = self.torrents(); let mut start_hashes = vec![]; // We need to get the hashes of the torrents to be able to start all of them for i in 0..model.n_items() { let torrent = model.item(i).unwrap().downcast::().unwrap(); start_hashes.push(torrent.hash()); } if let Some(client) = self.rpc_client() { client .torrent_start(Some(start_hashes), false) .compat() .await?; self.refresh_data().await; } else { warn!("Unable to start torrents, no rpc connection") } Ok(()) } pub async fn stop_torrents(&self) -> Result<(), ClientError> { let model = self.torrents(); let mut stop_hashes = vec![]; for i in 0..model.n_items() { let torrent = model.item(i).unwrap().downcast::().unwrap(); stop_hashes.push(torrent.hash()); } if let Some(client) = self.rpc_client() { client.torrent_stop(Some(stop_hashes)).compat().await?; // The daemon needs a short time to stop the torrents glib::timeout_future(Duration::from_millis(500)).await; self.refresh_data().await; } else { warn!("Unable to stop torrents, no rpc connection"); } Ok(()) } /// Polls the latest information without waiting for the next polling /// timeout pub async fn refresh_data(&self) { if let Err(err) = self.imp().poll_data(false).await { warn!("Couldn't poll transmission data: {err}"); self.clone().disconnect(false).await; self.emit_by_name::<()>("connection-failure", &[]); } } pub(crate) fn rpc_client(&self) -> Option { let imp = self.imp(); imp.client.borrow().clone() } } transmission-gobject-0.1.7/src/encryption.rs000064400000000000000000000021011046102023000173160ustar 00000000000000use glib::Enum; use transmission_client::Encryption; #[derive(Default, Copy, Debug, Clone, PartialEq, Enum)] #[repr(u32)] #[enum_type(name = "TrEncrypption")] pub enum TrEncryption { Required, #[default] Preferred, Tolerated, } impl From for TrEncryption { fn from(u: u32) -> Self { match u { 0 => Self::Required, 1 => Self::Preferred, 2 => Self::Tolerated, _ => Self::default(), } } } impl From for TrEncryption { fn from(enc: Encryption) -> Self { match enc { Encryption::Required => TrEncryption::Required, Encryption::Preferred => TrEncryption::Preferred, Encryption::Tolerated => TrEncryption::Tolerated, } } } impl From for Encryption { fn from(val: TrEncryption) -> Self { match val { TrEncryption::Required => Encryption::Required, TrEncryption::Preferred => Encryption::Preferred, TrEncryption::Tolerated => Encryption::Tolerated, } } } transmission-gobject-0.1.7/src/file.rs000064400000000000000000000243101046102023000160510ustar 00000000000000use std::cell::{Cell, OnceCell, RefCell}; use std::collections::{HashMap, HashSet}; use gio::prelude::*; use glib::subclass::prelude::{ObjectSubclass, *}; use glib::{Properties, clone}; use transmission_client::{File, FileStat}; use crate::{TrRelatedModel, TrTorrent}; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrFile)] pub struct TrFile { #[property(get, set, construct_only)] torrent: OnceCell, #[property(get, set, construct_only)] id: Cell, #[property(get, set, construct_only)] name: OnceCell, #[property(get, set, construct_only)] title: OnceCell, #[property(get, set, construct_only)] is_folder: Cell, #[property(get)] pub bytes_completed: Cell, #[property(get, set, construct_only)] length: Cell, #[property(get, set = Self::set_wanted)] pub wanted: Cell, #[property(get)] wanted_inconsistent: Cell, #[property(get)] related: TrRelatedModel, related_wanted: RefCell>, related_length: RefCell>, related_bytes_completed: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for TrFile { const NAME: &'static str = "TrFile"; type ParentType = glib::Object; type Type = super::TrFile; } #[glib::derived_properties] impl ObjectImpl for TrFile {} impl TrFile { fn set_wanted(&self, wanted: bool) { let fut = clone!( #[weak(rename_to = this)] self, async move { // Collect ids of files which shall get updated let ids = if this.obj().is_folder() { let path = this.obj().name(); // We can't use `related()` here, since that wouldn't return all nested folders / files let files = this.obj().torrent().files().related_files_by_path(&path); let mut ids = Vec::new(); for file in files { ids.push(file.id()); } ids } else { vec![this.obj().id()] }; if wanted { this.torrent .get() .unwrap() .set_wanted_files(ids) .await .unwrap(); } else { this.torrent .get() .unwrap() .set_unwanted_files(ids) .await .unwrap(); } this.wanted.set(wanted); this.obj().notify_wanted(); } ); glib::spawn_future_local(fut); } pub fn find_title(name: &str) -> String { if !name.contains('/') { return name.to_string(); } let slashes = name.match_indices('/'); name[(slashes.clone().next_back().unwrap().0) + 1..name.len()].to_string() } /// Update `self` when a related file changes its `wanted` state pub fn update_related_wanted(&self, related_file: &super::TrFile) { assert!(self.obj().is_folder()); if related_file.wanted() && !related_file.wanted_inconsistent() { self.related_wanted.borrow_mut().insert(related_file.name()); } else { self.related_wanted .borrow_mut() .remove(&related_file.name()); } // Check if this folder is in a inconsistent state // (mixed wanted/not wanted related files) let files_count = self.obj().related().n_items() as usize; let wanted_count = self.related_wanted.borrow().len(); if files_count == wanted_count { self.wanted.set(true); self.wanted_inconsistent.set(false); } else if wanted_count == 0 { self.wanted.set(false); self.wanted_inconsistent.set(false); } else { self.wanted.set(true); self.wanted_inconsistent.set(true); } self.obj().notify_wanted(); self.obj().notify_wanted_inconsistent(); } /// Update `self` when a related file changes its `length` state pub fn update_related_length(&self, related_file: &super::TrFile) { assert!(self.obj().is_folder()); let obj = self.obj(); let mut related_length = self.related_length.borrow_mut(); let mut value_changed = false; let mut previous_value: i64 = 0; if let Some(length) = related_length.get(&related_file.name()) { if length != &related_file.length() { value_changed = true; previous_value = *length; } } else { related_length.insert(related_file.name(), related_file.length()); self.length.set(obj.length() + related_file.length()); self.obj().notify_length(); } if value_changed { related_length.insert(related_file.name(), related_file.length()); self.length.set(obj.length() - previous_value); self.length.set(obj.length() + related_file.length()); self.obj().notify_length(); } } /// Update `self` when a related file changes its `bytes_completed` /// state pub fn update_related_bytes_completed(&self, related_file: &super::TrFile) { assert!(self.obj().is_folder()); let obj = self.obj(); let mut related_bytes_completed = self.related_bytes_completed.borrow_mut(); let mut value_changed = false; let mut previous_value: i64 = 0; if let Some(bytes_completed) = related_bytes_completed.get(&related_file.name()) { if bytes_completed != &related_file.bytes_completed() { value_changed = true; previous_value = *bytes_completed; } } else { related_bytes_completed.insert(related_file.name(), related_file.bytes_completed()); self.bytes_completed .set(obj.bytes_completed() + related_file.bytes_completed()); self.obj().notify_bytes_completed(); } if value_changed { related_bytes_completed.insert(related_file.name(), related_file.bytes_completed()); self.bytes_completed .set(obj.bytes_completed() - previous_value); self.bytes_completed .set(obj.bytes_completed() + related_file.bytes_completed()); self.obj().notify_bytes_completed(); } } } } glib::wrapper! { pub struct TrFile(ObjectSubclass); } impl TrFile { pub(crate) fn from_rpc_file(id: i32, rpc_file: &File, torrent: &TrTorrent) -> Self { let name = rpc_file.name.clone(); let title = imp::TrFile::find_title(&name); glib::Object::builder() .property("id", id) .property("name", &name) .property("title", &title) .property("length", rpc_file.length) .property("is-folder", false) .property("torrent", torrent) .build() } pub(crate) fn new_folder(name: &str, torrent: &TrTorrent) -> Self { let title = imp::TrFile::find_title(name); glib::Object::builder() .property("id", -1) .property("name", name) .property("title", &title) .property("is-folder", true) .property("torrent", torrent) .build() } /// Add a new related [TrFile] to `self` (which is a folder) pub(crate) fn add_related(&self, file: &TrFile) { assert!(self.is_folder()); let imp = self.imp(); self.related().add_file(file); file.connect_notify_local( Some("wanted"), clone!( #[weak(rename_to = this)] imp, move |file, _| { this.update_related_wanted(file); } ), ); file.connect_notify_local( Some("wanted-inconsistent"), clone!( #[weak(rename_to = this)] imp, move |file, _| { this.update_related_wanted(file); } ), ); imp.update_related_wanted(file); file.connect_notify_local( Some("length"), clone!( #[weak(rename_to = this)] imp, move |file, _| { this.update_related_length(file); } ), ); imp.update_related_length(file); file.connect_notify_local( Some("bytes-completed"), clone!( #[weak(rename_to = this)] imp, move |file, _| { this.update_related_bytes_completed(file); } ), ); imp.update_related_bytes_completed(file); } /// Updates the values of `self` (which is not a folder) pub(crate) fn refresh_values(&self, rpc_file_stat: &FileStat) { assert!(!self.is_folder()); let imp = self.imp(); // bytes_completed if imp.bytes_completed.get() != rpc_file_stat.bytes_completed { imp.bytes_completed.set(rpc_file_stat.bytes_completed); self.notify_bytes_completed(); } // wanted if imp.wanted.get() != rpc_file_stat.wanted { imp.wanted.set(rpc_file_stat.wanted); self.notify_wanted(); } } } transmission-gobject-0.1.7/src/file_model.rs000064400000000000000000000144121046102023000172330ustar 00000000000000use std::cell::{Cell, RefCell}; use gio::prelude::*; use gio::subclass::prelude::*; use glib::Properties; use indexmap::map::IndexMap; use transmission_client::TorrentFiles; use crate::{TrFile, TrTorrent}; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrFileModel)] pub struct TrFileModel { /// The top level file #[property(get, nullable)] pub top_level: RefCell>, /// Whether this contains data, and can be consumed #[property(get)] pub is_ready: Cell, /// All files and folders (Full path + TrFile) pub map: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for TrFileModel { const NAME: &'static str = "TrFileModel"; type ParentType = glib::Object; type Type = super::TrFileModel; type Interfaces = (gio::ListModel,); } #[glib::derived_properties] impl ObjectImpl for TrFileModel {} impl ListModelImpl for TrFileModel { fn item_type(&self) -> glib::Type { TrFile::static_type() } fn n_items(&self) -> u32 { self.map.borrow().len() as u32 } fn item(&self, position: u32) -> Option { self.map .borrow() .get_index(position.try_into().unwrap()) .map(|(_, o)| o.clone().upcast::()) } } impl TrFileModel { pub fn add_file(&self, file: &TrFile, torrent: &TrTorrent) { if self.obj().file_by_name(&file.name()).is_some() { warn!("File {:?} already exists in model", file.name()); return; } let mut map = self.map.borrow_mut(); let full_path = file.name(); // Resolve individual folders to make sure that they're added as TrFile to the // model eg. "there/can/be/nested/folders/file.txt" // -> there, can, be, nested, folders let mut folder_names = Vec::new(); let folder_name = if full_path.contains('/') { let slashes = full_path.match_indices('/'); let folder_name = full_path[..slashes.clone().next_back().unwrap().0].to_string(); for slash in slashes { let folder_name = full_path[..slash.0].to_string(); folder_names.push(folder_name); } Some(folder_name) } else { // No folders, it's a single file torrent self.top_level.borrow_mut().replace(file.clone()); self.obj().notify_top_level(); None }; // Make sure that nested folders are related to their parent let mut parent: Option = None; for folder_name in folder_names { let folder = if let Some(folder) = map.get(&folder_name) { folder.clone() } else { let folder = TrFile::new_folder(&folder_name, torrent); // We know that the folder is related / a subfolder of the parent if let Some(parent) = parent { parent.add_related(&folder); } else { // No parent -> the current file is the toplevel folder self.top_level.borrow_mut().replace(folder.clone()); self.obj().notify_top_level(); } map.insert(folder_name.clone(), folder.clone()); folder }; parent = Some(folder); } // Now since we made sure that all folders are added, // we can take care of the actual file map.insert(full_path.clone(), file.clone()); let pos = (map.len() - 1) as u32; self.obj().items_changed(pos, 0, 1); if let Some(folder_name) = folder_name { // Get the actual folder to add the file as related file (child) map.get_mut(&folder_name).unwrap().add_related(file); } } } } glib::wrapper! { pub struct TrFileModel(ObjectSubclass) @implements gio::ListModel; } impl TrFileModel { pub(crate) fn refresh_files(&self, rpc_files: &TorrentFiles, torrent: &TrTorrent) { let is_initial = self.top_level().is_none(); for (index, rpc_file) in rpc_files.files.iter().enumerate() { let rpc_file_stat = rpc_files.file_stats.get(index).cloned().unwrap_or_default(); if let Some(file) = self.file_by_name(&rpc_file.name) { file.refresh_values(&rpc_file_stat); } else { let file = TrFile::from_rpc_file(index.try_into().unwrap(), rpc_file, torrent); file.refresh_values(&rpc_file_stat); self.imp().add_file(&file, torrent); } } if is_initial && self.top_level().is_some() { self.imp().is_ready.set(true); self.notify_is_ready(); } } pub(crate) fn related_files_by_path(&self, path: &str) -> Vec { let imp = self.imp(); let mut result = Vec::new(); for (file_path, file) in &*imp.map.borrow() { if file_path.contains(path) && !file.is_folder() { result.push(file.clone()); } } result } /// Returns a [TrFile] based on its name (path) pub fn file_by_name(&self, name: &str) -> Option { self.imp() .map .borrow() .get(name) .map(|o| o.clone().downcast().unwrap()) } /// Returns parent folder for [TrFile] pub fn parent(&self, folder: &TrFile) -> Option { let folder_name = folder.name(); if folder_name.contains('/') { let slashes = folder_name.match_indices('/'); let parent_name = folder.name()[0..slashes.clone().next_back().unwrap().0].to_string(); let parent = self.file_by_name(&parent_name); return parent; } None } } impl Default for TrFileModel { fn default() -> Self { glib::Object::new() } } transmission-gobject-0.1.7/src/lib.rs000064400000000000000000000013001046102023000156720ustar 00000000000000#[macro_use] extern crate log; mod authentication; mod client; mod encryption; mod file; mod file_model; mod related_model; mod session; mod session_stats; mod session_stats_details; mod torrent; mod torrent_model; mod torrent_status; pub use authentication::TrAuthentication; pub use client::TrClient; pub use encryption::TrEncryption; pub use file::TrFile; pub use file_model::TrFileModel; pub use related_model::TrRelatedModel; pub use session::TrSession; pub use session_stats::TrSessionStats; pub use session_stats_details::TrSessionStatsDetails; pub use torrent::TrTorrent; pub use torrent_model::TrTorrentModel; pub use torrent_status::TrTorrentStatus; pub use transmission_client::ClientError; transmission-gobject-0.1.7/src/related_model.rs000064400000000000000000000035131046102023000177340ustar 00000000000000use std::cell::RefCell; use gio::prelude::*; use gio::subclass::prelude::*; use indexmap::map::IndexMap; use crate::TrFile; mod imp { use super::*; #[derive(Debug, Default)] pub struct TrRelatedModel { pub map: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for TrRelatedModel { const NAME: &'static str = "TrRelatedModel"; type Type = super::TrRelatedModel; type Interfaces = (gio::ListModel,); } impl ObjectImpl for TrRelatedModel {} impl ListModelImpl for TrRelatedModel { fn item_type(&self) -> glib::Type { TrFile::static_type() } fn n_items(&self) -> u32 { self.map.borrow().len() as u32 } fn item(&self, position: u32) -> Option { self.map .borrow() .get_index(position.try_into().unwrap()) .map(|(_, o)| o.clone().upcast::()) } } impl TrRelatedModel {} } glib::wrapper! { pub struct TrRelatedModel(ObjectSubclass) @implements gio::ListModel; } impl TrRelatedModel { pub fn new() -> Self { glib::Object::new() } pub(crate) fn add_file(&self, file: &TrFile) { let pos = { let mut map = self.imp().map.borrow_mut(); if map.contains_key(&file.name()) { warn!( "Model already contains file {} with name {}", file.title(), file.name() ); return; } map.insert(file.name(), file.clone()); (map.len() - 1) as u32 }; self.items_changed(pos, 0, 1); } } impl Default for TrRelatedModel { fn default() -> Self { Self::new() } } transmission-gobject-0.1.7/src/session.rs000064400000000000000000000273631046102023000166300ustar 00000000000000use std::cell::{Cell, OnceCell, RefCell}; use async_compat::CompatExt; use gio::prelude::FileExt; use glib::object::ObjectExt; use glib::subclass::prelude::{ObjectSubclass, *}; use glib::{Properties, clone}; use transmission_client::{Session, SessionMutator}; use crate::{TrClient, TrEncryption}; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrSession)] pub struct TrSession { #[property(get, set, construct_only)] pub client: OnceCell, #[property(get)] pub version: RefCell, #[property(get, set = Self::set_download_dir)] pub download_dir: RefCell>, #[property(get, set = Self::set_start_added_torrents)] pub start_added_torrents: Cell, #[property(get, set = Self::set_encryption, builder(Default::default()))] pub encryption: Cell, #[property(get, set = Self::set_incomplete_dir_enabled)] pub incomplete_dir_enabled: Cell, #[property(get, set = Self::set_incomplete_dir)] pub incomplete_dir: RefCell>, #[property(get, set = Self::set_download_queue_enabled)] pub download_queue_enabled: Cell, #[property(get, set = Self::set_download_queue_size, minimum = 1, default_value = 1)] pub download_queue_size: Cell, #[property(get, set = Self::set_seed_queue_enabled)] pub seed_queue_enabled: Cell, #[property(get, set = Self::set_seed_queue_size, minimum = 1, default_value = 1)] pub seed_queue_size: Cell, #[property(get, set = Self::set_port_forwarding_enabled)] pub port_forwarding_enabled: Cell, #[property(get, set = Self::set_peer_port_random_on_start)] pub peer_port_random_on_start: Cell, #[property(get, set = Self::set_peer_port, minimum = 1, default_value = 1)] pub peer_port: Cell, #[property(get, set = Self::set_peer_limit_global, minimum = 1, default_value = 1)] pub peer_limit_global: Cell, #[property(get, set = Self::set_peer_limit_per_torrent, minimum = 1, default_value = 1)] pub peer_limit_per_torrent: Cell, } #[glib::object_subclass] impl ObjectSubclass for TrSession { const NAME: &'static str = "TrSession"; type ParentType = glib::Object; type Type = super::TrSession; } #[glib::derived_properties] impl ObjectImpl for TrSession {} impl TrSession { pub fn set_download_dir(&self, value: gio::File) { *self.download_dir.borrow_mut() = Some(value.clone()); let mutator = SessionMutator { download_dir: Some(value.path().unwrap()), ..Default::default() }; self.mutate_session(mutator, "download_dir"); } pub fn set_start_added_torrents(&self, value: bool) { self.start_added_torrents.set(value); let mutator = SessionMutator { start_added_torrents: Some(value), ..Default::default() }; self.mutate_session(mutator, "start-added-torrents"); } pub fn set_encryption(&self, value: TrEncryption) { self.encryption.set(value); let mutator = SessionMutator { encryption: Some(value.into()), ..Default::default() }; self.mutate_session(mutator, "encryption"); } pub fn set_incomplete_dir_enabled(&self, value: bool) { self.incomplete_dir_enabled.set(value); let mutator = SessionMutator { incomplete_dir_enabled: Some(value), ..Default::default() }; self.mutate_session(mutator, "incomplete-dir-enabled"); } pub fn set_incomplete_dir(&self, value: gio::File) { *self.incomplete_dir.borrow_mut() = Some(value.clone()); let mutator = SessionMutator { incomplete_dir: Some(value.path().unwrap()), ..Default::default() }; self.mutate_session(mutator, "incomplete-dir"); } pub fn set_download_queue_enabled(&self, value: bool) { self.download_queue_enabled.set(value); let mutator = SessionMutator { download_queue_enabled: Some(value), ..Default::default() }; self.mutate_session(mutator, "download-queue-enabled"); } pub fn set_download_queue_size(&self, value: i32) { self.download_queue_size.set(value); let mutator = SessionMutator { download_queue_size: Some(value), ..Default::default() }; self.mutate_session(mutator, "download-queue-size"); } pub fn set_seed_queue_enabled(&self, value: bool) { self.seed_queue_enabled.set(value); let mutator = SessionMutator { seed_queue_enabled: Some(value), ..Default::default() }; self.mutate_session(mutator, "seed-queue-enabled"); } pub fn set_seed_queue_size(&self, value: i32) { self.seed_queue_size.set(value); let mutator = SessionMutator { seed_queue_size: Some(value), ..Default::default() }; self.mutate_session(mutator, "seed-queue-size"); } pub fn set_port_forwarding_enabled(&self, value: bool) { self.port_forwarding_enabled.set(value); let mutator = SessionMutator { port_forwarding_enabled: Some(value), ..Default::default() }; self.mutate_session(mutator, "port-forwarding-enabled"); } pub fn set_peer_port_random_on_start(&self, value: bool) { self.peer_port_random_on_start.set(value); let mutator = SessionMutator { peer_port_random_on_start: Some(value), ..Default::default() }; self.mutate_session(mutator, "peer-port-random-on-start"); } pub fn set_peer_port(&self, value: i32) { self.peer_port.set(value); let mutator = SessionMutator { peer_port: Some(value), ..Default::default() }; self.mutate_session(mutator, "peer-port"); } pub fn set_peer_limit_global(&self, value: i32) { self.peer_limit_global.set(value); let mutator = SessionMutator { peer_limit_global: Some(value), ..Default::default() }; self.mutate_session(mutator, "peer-limit-global"); } pub fn set_peer_limit_per_torrent(&self, value: i32) { self.peer_limit_per_torrent.set(value); let mutator = SessionMutator { peer_limit_per_torrent: Some(value), ..Default::default() }; self.mutate_session(mutator, "peer-limit-per-torrent"); } fn mutate_session(&self, mutator: SessionMutator, prop_name: &str) { self.obj().notify(prop_name); let fut = clone!( #[weak(rename_to = this)] self, async move { if let Some(rpc_client) = this.client.get().unwrap().rpc_client() { rpc_client.session_set(mutator).compat().await.unwrap(); } else { warn!("Unable set mutate session, no rpc connection."); } } ); glib::spawn_future_local(fut); } } } glib::wrapper! { pub struct TrSession(ObjectSubclass); } impl TrSession { pub(crate) fn new(client: &TrClient) -> Self { glib::Object::builder().property("client", client).build() } pub(crate) fn refresh_values(&self, rpc_session: Session) { let imp = self.imp(); // version if *imp.version.borrow() != rpc_session.version { *imp.version.borrow_mut() = rpc_session.version; self.notify_version(); } // download_dir let download_dir = gio::File::for_path(rpc_session.download_dir); if download_dir.path() != imp.download_dir.borrow().as_ref().and_then(|f| f.path()) { *imp.download_dir.borrow_mut() = Some(download_dir); self.notify_download_dir(); } // start_added_torrents if imp.start_added_torrents.get() != rpc_session.start_added_torrents { imp.start_added_torrents .set(rpc_session.start_added_torrents); self.notify_start_added_torrents(); } // encryption if imp.encryption.get() != rpc_session.encryption.clone().into() { imp.encryption.set(rpc_session.encryption.into()); self.notify_encryption(); } // incomplete_dir_enabled if imp.incomplete_dir_enabled.get() != rpc_session.incomplete_dir_enabled { imp.incomplete_dir_enabled .set(rpc_session.incomplete_dir_enabled); self.notify_incomplete_dir_enabled(); } // incomplete_dir let incomplete_dir = gio::File::for_path(rpc_session.incomplete_dir); if incomplete_dir.path() != imp.incomplete_dir.borrow().as_ref().and_then(|f| f.path()) { *imp.incomplete_dir.borrow_mut() = Some(incomplete_dir); self.notify_incomplete_dir(); } // download_queue_enabled if imp.download_queue_enabled.get() != rpc_session.download_queue_enabled { imp.download_queue_enabled .set(rpc_session.download_queue_enabled); self.notify_download_queue_enabled(); } // download_queue_size if imp.download_queue_size.get() != rpc_session.download_queue_size { imp.download_queue_size.set(rpc_session.download_queue_size); self.notify_download_queue_size(); } // seed_queue_enabled if imp.seed_queue_enabled.get() != rpc_session.seed_queue_enabled { imp.seed_queue_enabled.set(rpc_session.seed_queue_enabled); self.notify_seed_queue_enabled(); } // seed_queue_size if imp.seed_queue_size.get() != rpc_session.seed_queue_size { imp.seed_queue_size.set(rpc_session.seed_queue_size); self.notify_seed_queue_size(); } // port_forwarding_enabled if imp.port_forwarding_enabled.get() != rpc_session.port_forwarding_enabled { imp.port_forwarding_enabled .set(rpc_session.port_forwarding_enabled); self.notify_port_forwarding_enabled(); } // peer_port_random_on_start if imp.peer_port_random_on_start.get() != rpc_session.peer_port_random_on_start { imp.peer_port_random_on_start .set(rpc_session.peer_port_random_on_start); self.notify_peer_port_random_on_start(); } // peer_port if imp.peer_port.get() != rpc_session.peer_port { imp.peer_port.set(rpc_session.peer_port); self.notify_peer_port(); } // peer_limit_global if imp.peer_limit_global.get() != rpc_session.peer_limit_global { imp.peer_limit_global.set(rpc_session.peer_limit_global); self.notify_peer_limit_global(); } // peer_limit_per_torrent if imp.peer_limit_per_torrent.get() != rpc_session.peer_limit_per_torrent { imp.peer_limit_per_torrent .set(rpc_session.peer_limit_per_torrent); self.notify_peer_limit_per_torrent(); } } } transmission-gobject-0.1.7/src/session_stats.rs000064400000000000000000000062131046102023000200350ustar 00000000000000use std::cell::Cell; use glib::Properties; use glib::object::ObjectExt; use glib::subclass::prelude::{ObjectSubclass, *}; use transmission_client::SessionStats; use crate::TrSessionStatsDetails; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrSessionStats)] pub struct TrSessionStats { #[property(get, minimum = 1, default_value = 1)] pub torrent_count: Cell, #[property(get, minimum = 1, default_value = 1)] pub active_torrent_count: Cell, #[property(get, minimum = 1, default_value = 1)] pub paused_torrent_count: Cell, #[property(get, minimum = 1, default_value = 1)] pub downloaded_torrent_count: Cell, #[property(get, minimum = 1, default_value = 1)] pub download_speed: Cell, #[property(get, minimum = 1, default_value = 1)] pub upload_speed: Cell, #[property(get)] pub cumulative_stats: TrSessionStatsDetails, #[property(get)] pub current_stats: TrSessionStatsDetails, } #[glib::object_subclass] impl ObjectSubclass for TrSessionStats { const NAME: &'static str = "TrSessionStats"; type ParentType = glib::Object; type Type = super::TrSessionStats; } #[glib::derived_properties] impl ObjectImpl for TrSessionStats {} } glib::wrapper! { pub struct TrSessionStats(ObjectSubclass); } impl TrSessionStats { pub(crate) fn refresh_values(&self, rpc_stats: SessionStats, download_count: i32) { let imp = self.imp(); // torrent_count if imp.torrent_count.get() != rpc_stats.torrent_count { imp.torrent_count.set(rpc_stats.torrent_count); self.notify_torrent_count(); } // active_torrent_count if imp.active_torrent_count.get() != rpc_stats.active_torrent_count { imp.active_torrent_count.set(rpc_stats.active_torrent_count); self.notify_active_torrent_count(); } // paused_torrent_count if imp.paused_torrent_count.get() != rpc_stats.paused_torrent_count { imp.paused_torrent_count.set(rpc_stats.paused_torrent_count); self.notify_paused_torrent_count(); } // downloaded_torrent_count if imp.downloaded_torrent_count.get() != download_count { imp.downloaded_torrent_count.set(download_count); self.notify_downloaded_torrent_count(); } // download_speed if imp.download_speed.get() != rpc_stats.download_speed { imp.download_speed.set(rpc_stats.download_speed); self.notify_download_speed(); } // upload_speed if imp.upload_speed.get() != rpc_stats.upload_speed { imp.upload_speed.set(rpc_stats.upload_speed); self.notify_upload_speed(); } imp.cumulative_stats .refresh_values(rpc_stats.cumulative_stats); imp.current_stats.refresh_values(rpc_stats.current_stats); } } impl Default for TrSessionStats { fn default() -> Self { glib::Object::new() } } transmission-gobject-0.1.7/src/session_stats_details.rs000064400000000000000000000045011046102023000215400ustar 00000000000000use std::cell::Cell; use glib::Properties; use glib::object::ObjectExt; use glib::subclass::prelude::{ObjectSubclass, *}; use transmission_client::StatsDetails; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrSessionStatsDetails)] pub struct TrSessionStatsDetails { #[property(get)] pub seconds_active: Cell, #[property(get)] pub downloaded_bytes: Cell, #[property(get)] pub uploaded_bytes: Cell, #[property(get)] pub files_added: Cell, #[property(get)] pub session_count: Cell, } #[glib::object_subclass] impl ObjectSubclass for TrSessionStatsDetails { const NAME: &'static str = "TrSessionStatsDetails"; type ParentType = glib::Object; type Type = super::TrSessionStatsDetails; } #[glib::derived_properties] impl ObjectImpl for TrSessionStatsDetails {} } glib::wrapper! { pub struct TrSessionStatsDetails(ObjectSubclass); } impl TrSessionStatsDetails { pub(crate) fn refresh_values(&self, rpc_details: StatsDetails) { let imp = self.imp(); // seconds_active if imp.seconds_active.get() != rpc_details.seconds_active { imp.seconds_active.set(rpc_details.seconds_active); self.notify_seconds_active(); } // downloaded_bytes if imp.downloaded_bytes.get() != rpc_details.downloaded_bytes { imp.downloaded_bytes.set(rpc_details.downloaded_bytes); self.notify_downloaded_bytes(); } // uploaded_bytes if imp.uploaded_bytes.get() != rpc_details.uploaded_bytes { imp.uploaded_bytes.set(rpc_details.uploaded_bytes); self.notify_uploaded_bytes(); } // files_added if imp.files_added.get() != rpc_details.files_added { imp.files_added.set(rpc_details.files_added); self.notify_files_added(); } // session_count if imp.session_count.get() != rpc_details.session_count { imp.session_count.set(rpc_details.session_count); self.notify_session_count(); } } } impl Default for TrSessionStatsDetails { fn default() -> Self { glib::Object::new() } } transmission-gobject-0.1.7/src/torrent.rs000064400000000000000000000332541046102023000166360ustar 00000000000000use std::cell::{OnceCell, RefCell}; use std::time::Duration; use async_compat::CompatExt; use gio::File; use gio::prelude::*; use glib::property::PropertySet; use glib::subclass::prelude::{ObjectSubclass, *}; use glib::{Properties, clone}; use transmission_client::{ClientError, Torrent, TorrentFiles, TorrentMutator, TorrentPeers}; use crate::{TrClient, TrFileModel, TrTorrentStatus}; mod imp { use super::*; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::TrTorrent)] pub struct TrTorrent { // Static values #[property(get, set, construct_only)] pub name: OnceCell, #[property(get, set, construct_only)] pub hash: OnceCell, #[property(get, set, construct_only)] pub primary_mime_type: OnceCell, #[property(get, set, construct_only)] pub magnet_link: OnceCell, #[property(get, set, construct_only)] pub client: OnceCell, // Dynamic values #[property(get)] pub download_dir: RefCell, #[property(get)] pub size: RefCell, #[property(get, builder(Default::default()))] pub status: RefCell, #[property(get)] pub is_stalled: RefCell, #[property(get)] pub eta: RefCell, #[property(get)] pub error: RefCell, #[property(get)] pub error_string: RefCell, #[property(get)] pub progress: RefCell, #[property(get)] pub queue_position: RefCell, #[property(get)] pub download_queue_position: RefCell, #[property(get)] pub seed_queue_position: RefCell, #[property(get)] pub seeders_active: RefCell, #[property(get)] pub seeders: RefCell, #[property(get)] pub leechers: RefCell, #[property(get)] pub downloaded: RefCell, #[property(get)] pub uploaded: RefCell, #[property(get)] pub download_speed: RefCell, #[property(get)] pub upload_speed: RefCell, #[property(get)] pub metadata_percent_complete: RefCell, #[property(get)] pub files: TrFileModel, /// Whether to poll extra info like files or available peers, which data /// can be pretty expensive to deserialize #[property(get, set = Self::set_update_extra_info)] pub update_extra_info: RefCell, } #[glib::object_subclass] impl ObjectSubclass for TrTorrent { const NAME: &'static str = "TrTorrent"; type ParentType = glib::Object; type Type = super::TrTorrent; } #[glib::derived_properties] impl ObjectImpl for TrTorrent {} impl TrTorrent { fn set_update_extra_info(&self, value: bool) { self.update_extra_info.set(value); self.obj().notify_update_extra_info(); let fut = clone!( #[weak(rename_to = this)] self, async move { this.do_poll().await; } ); glib::spawn_future_local(fut); } pub async fn do_poll(&self) { self.obj().client().refresh_data().compat().await; } } } glib::wrapper! { pub struct TrTorrent(ObjectSubclass); } impl TrTorrent { pub fn from_rpc_torrent(rpc_torrent: &Torrent, client: &TrClient) -> Self { glib::Object::builder() .property("hash", &rpc_torrent.hash_string) .property("name", &rpc_torrent.name) .property("primary-mime-type", &rpc_torrent.primary_mime_type) .property("magnet-link", &rpc_torrent.magnet_link) .property("client", client) .build() } pub async fn start(&self, bypass_queue: bool) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { client .torrent_start(Some(vec![self.hash()]), bypass_queue) .compat() .await?; self.imp().do_poll().await; } else { warn!("Unable to start torrent, no rpc connection."); } Ok(()) } pub async fn stop(&self) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { client .torrent_stop(Some(vec![self.hash()])) .compat() .await?; // The daemon needs a short time to stop the torrent glib::timeout_future(Duration::from_millis(500)).await; self.imp().do_poll().await; } else { warn!("Unable to stop torrent, no rpc connection."); } Ok(()) } pub async fn remove(&self, delete_local_data: bool) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { client .torrent_remove(Some(vec![self.hash()]), delete_local_data) .compat() .await?; self.imp().do_poll().await; } else { warn!("Unable to stop torrent, no rpc connection."); } Ok(()) } pub async fn reannounce(&self) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { client .torrent_reannounce(Some(vec![self.hash()])) .compat() .await?; self.imp().do_poll().await; } else { warn!("Unable to stop torrent, no rpc connection."); } Ok(()) } pub async fn set_location(&self, location: File, move_data: bool) -> Result<(), ClientError> { let path = location.path().unwrap().to_str().unwrap().to_string(); if let Some(client) = self.client().rpc_client() { client .torrent_set_location(Some(vec![self.hash()]), path, move_data) .compat() .await?; self.imp().do_poll().await; } else { warn!("Unable to set torrent location, no rpc connection."); } Ok(()) } pub(crate) async fn set_wanted_files(&self, wanted: Vec) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { let mutator = TorrentMutator { files_wanted: Some(wanted), ..Default::default() }; client .torrent_set(Some(vec![self.hash()]), mutator) .compat() .await?; } else { warn!("Unable to update files, no rpc connection."); } Ok(()) } pub(crate) async fn set_unwanted_files(&self, wanted: Vec) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { let mutator = TorrentMutator { files_unwanted: Some(wanted), ..Default::default() }; client .torrent_set(Some(vec![self.hash()]), mutator) .compat() .await?; } else { warn!("Unable to update files, no rpc connection."); } Ok(()) } pub(crate) fn refresh_values( &self, rpc_torrent: &Torrent, rpc_files: &TorrentFiles, rpc_peers: &TorrentPeers, ) { let imp = self.imp(); // download_dir if *imp.download_dir.borrow() != rpc_torrent.download_dir { imp.download_dir .borrow_mut() .clone_from(&rpc_torrent.download_dir); self.notify_download_dir(); } // size if *imp.size.borrow() != rpc_torrent.size_when_done { *imp.size.borrow_mut() = rpc_torrent.size_when_done; self.notify_size(); } // status if *imp.status.borrow() as u32 != rpc_torrent.status as u32 { let status = TrTorrentStatus::try_from(rpc_torrent.status as u32).unwrap(); // Check if status changed from "Downloading" to "Seeding" // and emit TrClient `torrent-downloaded` signal if self.status() == TrTorrentStatus::Download && (status == TrTorrentStatus::SeedWait || status == TrTorrentStatus::Seed) { imp.client .get() .unwrap() .emit_by_name::<()>("torrent-downloaded", &[&self]); } *imp.status.borrow_mut() = status; self.notify_status(); } // is_stalled if *imp.is_stalled.borrow() as u32 != rpc_torrent.is_stalled as u32 { *imp.is_stalled.borrow_mut() = rpc_torrent.is_stalled; self.notify_is_stalled(); } // eta if *imp.eta.borrow() != rpc_torrent.eta { *imp.eta.borrow_mut() = rpc_torrent.eta; self.notify_eta(); } // error if *imp.error.borrow() != rpc_torrent.error { *imp.error.borrow_mut() = rpc_torrent.error; self.notify_error(); } // error-string if *imp.error_string.borrow() != rpc_torrent.error_string { imp.error_string .borrow_mut() .clone_from(&rpc_torrent.error_string); self.notify_error_string(); } // progress if *imp.progress.borrow() != rpc_torrent.percent_done { *imp.progress.borrow_mut() = rpc_torrent.percent_done; self.notify_progress(); } // queue_position if *imp.queue_position.borrow() != rpc_torrent.queue_position { *imp.queue_position.borrow_mut() = rpc_torrent.queue_position; self.notify_queue_position(); } // downloaded if *imp.downloaded.borrow() != rpc_torrent.have_valid { *imp.downloaded.borrow_mut() = rpc_torrent.have_valid; self.notify_downloaded(); } // uploaded if *imp.uploaded.borrow() != rpc_torrent.uploaded_ever { *imp.uploaded.borrow_mut() = rpc_torrent.uploaded_ever; self.notify_uploaded(); } // downloaded if *imp.download_speed.borrow() != rpc_torrent.rate_download { *imp.download_speed.borrow_mut() = rpc_torrent.rate_download; self.notify_download_speed(); } // uploaded if *imp.upload_speed.borrow() != rpc_torrent.rate_upload { *imp.upload_speed.borrow_mut() = rpc_torrent.rate_upload; self.notify_upload_speed(); } // metadata_percent_complete if *imp.metadata_percent_complete.borrow() != rpc_torrent.metadata_percent_complete { *imp.metadata_percent_complete.borrow_mut() = rpc_torrent.metadata_percent_complete; self.notify_metadata_percent_complete(); } // Torrent peers // seeders_active if *imp.seeders_active.borrow() != rpc_peers.peers_sending_to_us { *imp.seeders_active.borrow_mut() = rpc_peers.peers_sending_to_us; self.notify("seeders_active"); } // seeders if *imp.seeders.borrow() != rpc_peers.peers_connected { *imp.seeders.borrow_mut() = rpc_peers.peers_connected; self.notify("seeders"); } // leechers if *imp.leechers.borrow() != rpc_peers.peers_getting_from_us { *imp.leechers.borrow_mut() = rpc_peers.peers_getting_from_us; self.notify("leechers"); } // Torrent files self.files().refresh_files(rpc_files, self); } pub(crate) fn refresh_queue_positions(&self, download_queue_pos: i32, seed_queue_pos: i32) { let imp = self.imp(); // download_queue_position if *imp.download_queue_position.borrow() != download_queue_pos { *imp.download_queue_position.borrow_mut() = download_queue_pos; self.notify_download_queue_position(); } // seed_queue_position if *imp.seed_queue_position.borrow() != seed_queue_pos { *imp.seed_queue_position.borrow_mut() = seed_queue_pos; self.notify_seed_queue_position(); } } pub async fn set_queue_position(&self, pos: i32) -> Result<(), ClientError> { if let Some(client) = self.client().rpc_client() { let mutator = TorrentMutator { queue_position: Some(pos), ..Default::default() }; client .torrent_set(Some(vec![self.hash()]), mutator) .compat() .await?; } else { warn!("Unable set queue position, no rpc connection."); } Ok(()) } pub async fn set_download_queue_position(&self, pos: i32) -> Result<(), ClientError> { let torrents = self.client().torrents(); for i in 0..torrents.n_items() { let torrent: TrTorrent = torrents.item(i).unwrap().downcast().unwrap(); if torrent.download_queue_position() == pos { self.set_queue_position(torrent.queue_position()).await?; break; } } self.imp().do_poll().await; Ok(()) } pub async fn set_seed_queue_position(&self, pos: i32) -> Result<(), ClientError> { let torrents = self.client().torrents(); for i in 0..torrents.n_items() { let torrent: TrTorrent = torrents.item(i).unwrap().downcast().unwrap(); if torrent.seed_queue_position() == pos { self.set_queue_position(torrent.queue_position()).await?; break; } } self.imp().do_poll().await; Ok(()) } } transmission-gobject-0.1.7/src/torrent_model.rs000064400000000000000000000071211046102023000200100ustar 00000000000000use std::cell::RefCell; use gio::prelude::*; use gio::subclass::prelude::*; use glib::subclass::Signal; use once_cell::sync::Lazy; use crate::torrent::TrTorrent; mod imp { use super::*; #[derive(Debug, Default)] pub struct TrTorrentModel { pub vec: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for TrTorrentModel { const NAME: &'static str = "TrTorrentModel"; type ParentType = glib::Object; type Type = super::TrTorrentModel; type Interfaces = (gio::ListModel,); } impl ObjectImpl for TrTorrentModel { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![ Signal::builder("status-changed") .flags(glib::SignalFlags::ACTION) .build(), ] }); SIGNALS.as_ref() } } impl ListModelImpl for TrTorrentModel { fn item_type(&self) -> glib::Type { TrTorrent::static_type() } fn n_items(&self) -> u32 { self.vec.borrow().len() as u32 } fn item(&self, position: u32) -> Option { self.vec .borrow() .get(position as usize) .map(|o| o.clone().upcast::()) } } impl TrTorrentModel { pub fn find(&self, hash: String) -> Option { for pos in 0..self.obj().n_items() { let obj = self.obj().item(pos)?; let s = obj.downcast::().unwrap(); if s.hash() == hash { return Some(pos); } } None } } } glib::wrapper! { pub struct TrTorrentModel(ObjectSubclass) @implements gio::ListModel; } impl TrTorrentModel { pub(crate) fn add_torrent(&self, torrent: &TrTorrent) { let imp = self.imp(); if self.imp().find(torrent.hash()).is_some() { warn!("Torrent {:?} already exists in model", torrent.name()); return; } // Own scope to avoid "already mutably borrowed: BorrowError" let pos = { let mut data = imp.vec.borrow_mut(); data.push(torrent.clone()); (data.len() - 1) as u32 }; self.items_changed(pos, 0, 1); } pub(crate) fn remove_torrent(&self, torrent: &TrTorrent) { let imp = self.imp(); match self.imp().find(torrent.hash()) { Some(pos) => { imp.vec.borrow_mut().remove(pos as usize); self.items_changed(pos, 1, 0); } None => warn!("Torrent {:?} not found in model", torrent.name()), } } pub fn torrent_by_hash(&self, hash: String) -> Option { if let Some(index) = self.imp().find(hash) { return Some(self.item(index).unwrap().downcast().unwrap()); } None } pub(crate) fn clear(&self) { let imp = self.imp(); let len = self.n_items(); imp.vec.borrow_mut().clear(); self.items_changed(0, len, 0); } pub(crate) fn get_hashes(&self) -> Vec { let mut hashes = Vec::new(); for pos in 0..self.n_items() { let obj = self.item(pos).unwrap(); let s = obj.downcast::().unwrap(); hashes.insert(0, s.hash()); } hashes } } impl Default for TrTorrentModel { fn default() -> Self { glib::Object::new() } } transmission-gobject-0.1.7/src/torrent_status.rs000064400000000000000000000005261046102023000202350ustar 00000000000000use glib::Enum; use num_enum::TryFromPrimitive; #[derive(Default, Debug, Copy, Clone, Enum, PartialEq, TryFromPrimitive)] #[repr(u32)] #[enum_type(name = "TrTorrentStatus")] pub enum TrTorrentStatus { #[default] Stopped = 0, CheckWait = 1, Check = 2, DownloadWait = 3, Download = 4, SeedWait = 5, Seed = 6, }