Implement Deluge support

This commit is contained in:
Marc Plano-Lesay 2025-05-07 18:19:34 +10:00
parent 9c993d7c04
commit 31d124dd82
Signed by: kernald
GPG key ID: 66A41B08CC62A6CF
21 changed files with 659 additions and 72 deletions

263
Cargo.lock generated
View file

@ -312,6 +312,35 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
dependencies = [
"cookie",
"document-features",
"idna",
"log",
"publicsuffix",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -449,6 +478,15 @@ dependencies = [
"syn",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]]
name = "dsl_auto_type"
version = "0.1.3"
@ -463,6 +501,18 @@ dependencies = [
"syn",
]
[[package]]
name = "educe"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8"
dependencies = [
"enum-ordinalize",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.15.0"
@ -504,6 +554,26 @@ dependencies = [
"syn",
]
[[package]]
name = "enum-ordinalize"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5"
dependencies = [
"enum-ordinalize-derive",
]
[[package]]
name = "enum-ordinalize-derive"
version = "4.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "env_logger"
version = "0.10.2"
@ -530,7 +600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -603,6 +673,21 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@ -610,6 +695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@ -618,6 +704,34 @@ 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"
@ -636,10 +750,16 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@ -859,10 +979,10 @@ dependencies = [
"http 1.3.1",
"hyper 1.6.0",
"hyper-util",
"rustls",
"rustls 0.23.26",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tokio-rustls 0.26.2",
"tower-service",
"webpki-roots",
]
@ -1121,7 +1241,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1190,6 +1310,12 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "log"
version = "0.4.27"
@ -1424,6 +1550,26 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -1489,6 +1635,22 @@ dependencies = [
"yansi",
]
[[package]]
name = "psl-types"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]]
name = "publicsuffix"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
dependencies = [
"idna",
"psl-types",
]
[[package]]
name = "quinn"
version = "0.11.7"
@ -1501,7 +1663,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"rustls 0.23.26",
"socket2",
"thiserror 2.0.12",
"tokio",
@ -1520,7 +1682,7 @@ dependencies = [
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls 0.23.26",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
@ -1540,7 +1702,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1600,8 +1762,10 @@ dependencies = [
"diesel",
"diesel_migrations",
"directories",
"env_logger",
"figment",
"figment_file_provider_adapter",
"futures",
"log",
"magnet-url",
"multimap",
@ -1610,10 +1774,15 @@ dependencies = [
"regex",
"reqwest 0.12.15",
"roux",
"rustls 0.22.4",
"rustls-native-certs",
"serde",
"serde_json",
"tempfile",
"tokio",
"tokio-rustls 0.25.0",
"tokio-serde",
"tokio-util",
"transmission-rpc",
"url",
"urlencoding",
@ -1707,6 +1876,8 @@ checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
dependencies = [
"base64 0.22.1",
"bytes",
"cookie",
"cookie_store",
"encoding_rs",
"futures-core",
"futures-util",
@ -1727,7 +1898,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls 0.23.26",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"serde",
@ -1737,7 +1908,7 @@ dependencies = [
"system-configuration 0.6.1",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-rustls 0.26.2",
"tokio-socks",
"tower",
"tower-service",
@ -1797,7 +1968,21 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "rustls"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
dependencies = [
"log",
"ring",
"rustls-pki-types",
"rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
@ -1809,11 +1994,24 @@ dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"rustls-webpki 0.103.1",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
dependencies = [
"openssl-probe",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
@ -1841,6 +2039,17 @@ dependencies = [
"web-time",
]
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustls-webpki"
version = "0.103.1"
@ -2107,7 +2316,7 @@ dependencies = [
"getrandom 0.3.2",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2262,16 +2471,42 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls",
"rustls 0.23.26",
"tokio",
]
[[package]]
name = "tokio-serde"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf600e7036b17782571dd44fa0a5cea3c82f60db5137f774a325a76a0d6852b"
dependencies = [
"bytes",
"educe",
"futures-core",
"futures-sink",
"pin-project",
"serde",
"serde_json",
]
[[package]]
name = "tokio-socks"
version = "0.5.2"
@ -2639,7 +2874,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]

View file

@ -13,12 +13,15 @@ roux = "2.2.14"
figment = { version = "0.10", features = ["toml", "json", "env"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.44.2", features = ["rt", "rt-multi-thread", "macros"] }
tokio = { version = "1.44.2", features = ["rt", "rt-multi-thread", "macros", "io-util", "net"] }
regex = "1.11.1"
figment_file_provider_adapter = "0.1.1"
directories = "6.0.0"
log = "0.4.27"
color-eyre = "0.6.3"
tokio-rustls = "0.25.0"
rustls = "0.22.2"
rustls-native-certs = "0.7.0"
chrono = { version = "0.4", features = ["serde"] }
multimap = "0.10.0"
diesel = { version = "2.2.10", features = ["sqlite", "chrono"] }
@ -30,7 +33,11 @@ clap-verbosity-flag = "3.0.2"
pretty_env_logger = "0.5.0"
async-trait = "0.1.88"
console = "0.15.11"
reqwest = "0.12.15"
reqwest = { version = "0.12.15", features = ["cookies"] }
magnet-url = "2.0.0"
urlencoding = "2.1.3"
ntfy = "0.7.0"
tokio-util = "0.7.15"
env_logger = "0.10.2"
tokio-serde = { version = "0.9.0", features = ["json"] }
futures = "0.3.31"

View file

@ -0,0 +1 @@
DROP TABLE deluge_processed;

View file

@ -0,0 +1,9 @@
CREATE TABLE deluge_processed
(
id INTEGER PRIMARY KEY NOT NULL,
magnet_id INTEGER NOT NULL,
processed_at DATETIME NOT NULL,
FOREIGN KEY (magnet_id) REFERENCES magnets (id)
);
CREATE INDEX deluge_processed_magnet_id ON deluge_processed (magnet_id);

View file

@ -1,7 +1,8 @@
use crate::actions::action::{Action, ProcessedMagnets};
use crate::actions::bitmagnet::client::BitmagnetClient;
use crate::actions::bitmagnet::config::BitmagnetConfig;
use crate::db::{BitmagnetProcessedTable, Database};
use crate::actions::bitmagnet::db::BitmagnetProcessedTable;
use crate::db::Database;
use color_eyre::eyre::Result;
use log::{debug, warn};

View file

@ -0,0 +1,31 @@
use crate::db::ProcessedTable;
use crate::models::NewBitmagnetProcessed;
use crate::schema::bitmagnet_processed;
use color_eyre::eyre::Context;
use diesel::{QueryDsl, RunQueryDsl, SqliteConnection};
pub struct BitmagnetProcessedTable;
impl ProcessedTable for BitmagnetProcessedTable {
fn get_processed_ids(conn: &mut SqliteConnection) -> color_eyre::Result<Vec<i32>> {
bitmagnet_processed::table
.select(bitmagnet_processed::magnet_id)
.load(conn)
.wrap_err("Failed to load processed magnet IDs for Bitmagnet")
}
fn mark_processed(conn: &mut SqliteConnection, magnet_id: i32) -> color_eyre::Result<()> {
let now = chrono::Utc::now().naive_utc();
let new_processed = NewBitmagnetProcessed {
magnet_id,
processed_at: &now,
};
diesel::insert_into(bitmagnet_processed::table)
.values(&new_processed)
.execute(conn)
.wrap_err("Failed to mark magnet as processed by Bitmagnet")?;
Ok(())
}
}

View file

@ -1,6 +1,7 @@
mod action;
mod client;
mod config;
mod db;
pub use action::BitmagnetAction;
pub use config::BitmagnetConfig;

View file

@ -0,0 +1,65 @@
use crate::actions::action::{Action, ProcessedMagnets};
use crate::actions::deluge::client::DelugeClient;
use crate::actions::deluge::config::DelugeConfig;
use crate::actions::deluge::db::DelugeProcessedTable;
use crate::db::Database;
use color_eyre::eyre::Result;
use log::{debug, warn};
/// Action for submitting magnet links to Deluge
pub struct DelugeAction {
client: DelugeClient,
}
impl DelugeAction {
pub fn new(config: &DelugeConfig) -> Result<Self> {
let client = DelugeClient::new(config)?;
Ok(DelugeAction { client })
}
}
#[async_trait::async_trait]
impl Action for DelugeAction {
/// Return the name of the action
fn name() -> &'static str {
"Deluge"
}
fn get_name(&self) -> &'static str {
Self::name()
}
/// Process all unprocessed magnet links and return the list of processed magnets
async fn process_unprocessed_magnets(&mut self, db: &mut Database) -> Result<ProcessedMagnets> {
let unprocessed_magnets = db.get_unprocessed_magnets_for_table::<DelugeProcessedTable>()?;
let mut processed_magnets = Vec::new();
let mut failed_magnets = Vec::new();
self.client.login().await?;
for magnet in unprocessed_magnets {
match self.client.submit_magnet(&magnet.link).await {
Ok(_) => {
debug!(
"Successfully submitted magnet link to {}: {}",
Self::name(),
magnet.title
);
debug!("Magnet link: {}", magnet.link);
db.mark_magnet_processed_for_table::<DelugeProcessedTable>(magnet.id)?;
processed_magnets.push(magnet);
}
Err(e) => {
warn!("Failed to submit magnet link to {}: {}", Self::name(), e);
failed_magnets.push(magnet);
}
}
}
Ok(ProcessedMagnets {
success: processed_magnets,
failed: failed_magnets,
})
}
}

View file

@ -0,0 +1,159 @@
use std::net::ToSocketAddrs;
use color_eyre::eyre::{eyre, Result, WrapErr};
use futures::SinkExt;
use futures::StreamExt;
use log::{debug, info};
use serde_json::{json, Value};
use tokio::net::TcpStream;
use tokio_util::codec::{Framed, LengthDelimitedCodec};
use crate::actions::deluge::config::DelugeConfig;
#[derive(Debug)]
pub enum DelugeError {
AuthenticationFailed,
Rpc(String),
}
impl std::error::Error for DelugeError {}
impl std::fmt::Display for DelugeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DelugeError::AuthenticationFailed => write!(f, "Authentication failed"),
DelugeError::Rpc(s) => write!(f, "RPC error: {}", s),
}
}
}
pub struct DelugeClient {
config: DelugeConfig,
stream: Option<Framed<TcpStream, LengthDelimitedCodec>>,
request_id: u64,
}
impl DelugeClient {
pub fn new(config: &DelugeConfig) -> Result<Self> {
let config = config.clone();
Ok(DelugeClient {
config,
stream: None,
request_id: 0,
})
}
async fn connect(&mut self) -> Result<()> {
if self.stream.is_none() {
let addr = format!("{}:{}", self.config.host, self.config.port)
.to_socket_addrs()
.wrap_err_with(|| format!("Failed to resolve host: {}", self.config.host))?
.next()
.ok_or_else(|| eyre!("Failed to get socket address"))?;
let tcp_stream = TcpStream::connect(addr).await.wrap_err_with(|| {
format!(
"Failed to connect to Deluge daemon at {}:{}",
self.config.host, self.config.port
)
})?;
let length_delimited = LengthDelimitedCodec::new();
let framed = Framed::new(tcp_stream, length_delimited);
info!(
"Connected to Deluge daemon at {}:{}",
self.config.host, self.config.port
);
self.stream = Some(framed);
}
Ok(())
}
async fn send_request(&mut self, method: &str, params: Vec<Value>) -> Result<u64> {
self.connect().await?;
self.request_id += 1;
let request = json!({
"method": method,
"params": params,
"id": self.request_id,
});
debug!(
"Sending request: {}",
serde_json::to_string(&request).unwrap()
);
if let Some(stream) = &mut self.stream {
let bytes = serde_json::to_vec(&request)?;
stream.send(bytes.into()).await?;
Ok(self.request_id)
} else {
Err(eyre!("Stream is not initialized"))
}
}
async fn receive_response(&mut self, expected_id: u64) -> Result<Value> {
if let Some(stream) = &mut self.stream {
while let Some(response) = stream.next().await {
let bytes = response?;
let response: Value = serde_json::from_slice(&bytes)?;
debug!(
"Received response: {}",
serde_json::to_string(&response).unwrap()
);
if response.get("id") == Some(&json!(expected_id)) {
if let Some(error) = response.get("error") {
if !error.is_null() {
let error_string = serde_json::to_string(error)
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(DelugeError::Rpc(error_string).into());
}
}
if let Some(result) = response.get("result") {
return Ok(result.clone());
}
}
}
Err(eyre!("Connection closed prematurely"))
} else {
Err(eyre!("Stream is not initialized"))
}
}
pub async fn login(&mut self) -> Result<()> {
let auth_id = self
.send_request("auth.login", vec![json!(self.config.password)])
.await?;
let response = self.receive_response(auth_id).await?;
if response.as_bool().unwrap_or(false) {
info!("Successfully authenticated with Deluge daemon.");
Ok(())
} else {
Err(DelugeError::AuthenticationFailed.into())
}
}
pub async fn submit_magnet(&mut self, magnet_link: &str) -> Result<String> {
let add_torrent_id = self
.send_request(
"core.add_torrent_magnet",
vec![
json!(magnet_link),
json!({
"download_location": self.config.download_dir,
"paused": false,
}),
],
)
.await?;
let response = self.receive_response(add_torrent_id).await?;
if let Some(torrent_hash) = response.as_str() {
info!("Successfully added torrent with hash: {}", torrent_hash);
Ok(torrent_hash.to_string())
} else {
Err(DelugeError::Rpc("Failed to add torrent".to_string()).into())
}
}
}

View file

@ -0,0 +1,19 @@
use crate::app::Enableable;
use serde::{Deserialize, Serialize};
/// Configuration for the Deluge action
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelugeConfig {
pub enable: bool,
pub host: String,
pub login: String,
pub password: String,
pub port: u16,
pub download_dir: String,
}
impl Enableable for DelugeConfig {
fn is_enabled(&self) -> bool {
self.enable
}
}

31
src/actions/deluge/db.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::db::ProcessedTable;
use crate::models::NewDelugeProcessed;
use crate::schema::deluge_processed;
use color_eyre::eyre::Context;
use diesel::{QueryDsl, RunQueryDsl, SqliteConnection};
pub struct DelugeProcessedTable;
impl ProcessedTable for DelugeProcessedTable {
fn get_processed_ids(conn: &mut SqliteConnection) -> color_eyre::Result<Vec<i32>> {
deluge_processed::table
.select(deluge_processed::magnet_id)
.load(conn)
.wrap_err("Failed to load processed magnet IDs for Bitmagnet")
}
fn mark_processed(conn: &mut SqliteConnection, magnet_id: i32) -> color_eyre::Result<()> {
let now = chrono::Utc::now().naive_utc();
let new_processed = NewDelugeProcessed {
magnet_id,
processed_at: &now,
};
diesel::insert_into(deluge_processed::table)
.values(&new_processed)
.execute(conn)
.wrap_err("Failed to mark magnet as processed by Bitmagnet")?;
Ok(())
}
}

View file

@ -0,0 +1,7 @@
pub mod action;
pub mod client;
pub mod config;
pub mod db;
pub use action::DelugeAction;
pub use config::DelugeConfig;

View file

@ -1,4 +1,5 @@
pub mod action;
pub mod bitmagnet;
pub mod deluge;
pub mod factory;
pub mod transmission;

View file

@ -1,7 +1,8 @@
use crate::actions::action::{Action, ProcessedMagnets};
use crate::actions::transmission::client::TransmissionClient;
use crate::actions::transmission::config::TransmissionConfig;
use crate::db::{Database, TransmissionProcessedTable};
use crate::actions::transmission::db::TransmissionProcessedTable;
use crate::db::Database;
use color_eyre::eyre::Result;
use log::{debug, warn};

View file

@ -0,0 +1,31 @@
use crate::db::ProcessedTable;
use crate::models::NewTransmissionProcessed;
use crate::schema::transmission_processed;
use color_eyre::eyre::Context;
use diesel::{QueryDsl, RunQueryDsl, SqliteConnection};
pub struct TransmissionProcessedTable;
impl ProcessedTable for TransmissionProcessedTable {
fn get_processed_ids(conn: &mut SqliteConnection) -> color_eyre::Result<Vec<i32>> {
transmission_processed::table
.select(transmission_processed::magnet_id)
.load(conn)
.wrap_err("Failed to load processed magnet IDs for Transmission")
}
fn mark_processed(conn: &mut SqliteConnection, magnet_id: i32) -> color_eyre::Result<()> {
let now = chrono::Utc::now().naive_utc();
let new_processed = NewTransmissionProcessed {
magnet_id,
processed_at: &now,
};
diesel::insert_into(transmission_processed::table)
.values(&new_processed)
.execute(conn)
.wrap_err("Failed to mark magnet as processed by Transmission")?;
Ok(())
}
}

View file

@ -1,6 +1,7 @@
pub mod action;
pub mod client;
pub mod config;
pub mod db;
pub use action::TransmissionAction;
pub use config::TransmissionConfig;

View file

@ -1,5 +1,6 @@
use crate::actions::action::{Action, ProcessedMagnets};
use crate::actions::bitmagnet::BitmagnetAction;
use crate::actions::deluge::DelugeAction;
use crate::actions::factory::init_action;
use crate::actions::transmission::TransmissionAction;
use crate::config::Config;
@ -47,6 +48,9 @@ impl App {
if let Some(action) = init_action(&self.config.bitmagnet, BitmagnetAction::new)? {
self.actions.push(action);
}
if let Some(action) = init_action(&self.config.deluge, DelugeAction::new)? {
self.actions.push(action);
}
if let Some(action) = init_action(&self.config.transmission, TransmissionAction::new)? {
self.actions.push(action);
}

View file

@ -1,4 +1,5 @@
use crate::actions::bitmagnet::BitmagnetConfig;
use crate::actions::deluge::DelugeConfig;
use crate::actions::transmission::TransmissionConfig;
use crate::args::Args;
use crate::notifications::ntfy::NtfyConfig;
@ -31,6 +32,9 @@ pub struct Config {
#[serde(default)]
pub bitmagnet: Option<BitmagnetConfig>,
#[serde(default)]
pub deluge: Option<DelugeConfig>,
#[serde(default)]
pub transmission: Option<TransmissionConfig>,

View file

@ -1,5 +1,5 @@
use crate::models::{Magnet, NewBitmagnetProcessed, NewMagnet, NewTag, NewTransmissionProcessed};
use crate::schema::{bitmagnet_processed, magnet_tags, magnets, tags, transmission_processed};
use crate::models::{Magnet, NewMagnet, NewTag};
use crate::schema::{magnet_tags, magnets, tags};
use crate::PostInfo;
use color_eyre::eyre::{eyre, Result, WrapErr};
use diesel::prelude::*;
@ -16,57 +16,6 @@ pub trait ProcessedTable {
fn mark_processed(conn: &mut SqliteConnection, magnet_id: i32) -> Result<()>;
}
pub struct BitmagnetProcessedTable;
pub struct TransmissionProcessedTable;
impl ProcessedTable for BitmagnetProcessedTable {
fn get_processed_ids(conn: &mut SqliteConnection) -> Result<Vec<i32>> {
bitmagnet_processed::table
.select(bitmagnet_processed::magnet_id)
.load(conn)
.wrap_err("Failed to load processed magnet IDs for Bitmagnet")
}
fn mark_processed(conn: &mut SqliteConnection, magnet_id: i32) -> Result<()> {
let now = chrono::Utc::now().naive_utc();
let new_processed = NewBitmagnetProcessed {
magnet_id,
processed_at: &now,
};
diesel::insert_into(bitmagnet_processed::table)
.values(&new_processed)
.execute(conn)
.wrap_err("Failed to mark magnet as processed by Bitmagnet")?;
Ok(())
}
}
impl ProcessedTable for TransmissionProcessedTable {
fn get_processed_ids(conn: &mut SqliteConnection) -> Result<Vec<i32>> {
transmission_processed::table
.select(transmission_processed::magnet_id)
.load(conn)
.wrap_err("Failed to load processed magnet IDs for Transmission")
}
fn mark_processed(conn: &mut SqliteConnection, magnet_id: i32) -> Result<()> {
let now = chrono::Utc::now().naive_utc();
let new_processed = NewTransmissionProcessed {
magnet_id,
processed_at: &now,
};
diesel::insert_into(transmission_processed::table)
.values(&new_processed)
.execute(conn)
.wrap_err("Failed to mark magnet as processed by Transmission")?;
Ok(())
}
}
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
/// Database for storing magnet links and associated information

View file

@ -1,4 +1,6 @@
use crate::schema::{bitmagnet_processed, magnet_tags, magnets, tags, transmission_processed};
use crate::schema::{
bitmagnet_processed, deluge_processed, magnet_tags, magnets, tags, transmission_processed,
};
use chrono::NaiveDateTime;
use diesel::prelude::*;
@ -73,6 +75,24 @@ pub struct NewBitmagnetProcessed<'a> {
pub processed_at: &'a NaiveDateTime,
}
#[allow(dead_code)]
#[derive(Queryable, Selectable)]
#[diesel(table_name = deluge_processed)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct DelugeProcessed {
pub id: i32,
pub magnet_id: i32,
pub processed_at: NaiveDateTime,
}
#[derive(Insertable)]
#[diesel(table_name = deluge_processed)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct NewDelugeProcessed<'a> {
pub magnet_id: i32,
pub processed_at: &'a NaiveDateTime,
}
#[allow(dead_code)]
#[derive(Queryable, Selectable)]
#[diesel(table_name = transmission_processed)]

View file

@ -8,6 +8,14 @@ diesel::table! {
}
}
diesel::table! {
deluge_processed (id) {
id -> Integer,
magnet_id -> Integer,
processed_at -> Timestamp,
}
}
diesel::table! {
magnet_tags (magnet_id, tag_id) {
magnet_id -> Integer,
@ -43,12 +51,14 @@ diesel::table! {
}
diesel::joinable!(bitmagnet_processed -> magnets (magnet_id));
diesel::joinable!(deluge_processed -> magnets (magnet_id));
diesel::joinable!(magnet_tags -> magnets (magnet_id));
diesel::joinable!(magnet_tags -> tags (tag_id));
diesel::joinable!(transmission_processed -> magnets (magnet_id));
diesel::allow_tables_to_appear_in_same_query!(
bitmagnet_processed,
deluge_processed,
magnet_tags,
magnets,
tags,