From cbe7bc51a6c5ff9ab8b431fa3297634428fe26a3 Mon Sep 17 00:00:00 2001 From: "programmer@kl.netlib.re" Date: Sun, 16 Oct 2022 15:50:02 +0200 Subject: [PATCH] Load settings from -c/--config flag and print errors Ignore hidden files Add add subcommand --- .gitignore | 1 + src/.error.rs.swp | Bin 0 -> 12288 bytes src/action/.add.rs.swp | Bin 0 -> 12288 bytes src/action/add.rs | 24 +++++++++++++++++------- src/api.rs | 15 +++++++++++++++ src/cli.rs | 6 ++++++ src/config.rs | 12 +++++++++++- src/error.rs | 18 ++++++++++++++---- src/main.rs | 13 +++++++++++-- src/utils.rs | 3 +++ 10 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 src/.error.rs.swp create mode 100644 src/action/.add.rs.swp diff --git a/.gitignore b/.gitignore index ea8c4bf..7f4c33f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.* diff --git a/src/.error.rs.swp b/src/.error.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..229bff495e01be6add6f794c2046a9080d796567 GIT binary patch literal 12288 zcmeI2J!=#}7{@1CYE-lk3t>!}JxQ`LHen)x_<|e~5=hh{k+?6<-3{)}Zf0hq;W(`< z@(C=(%EH1@KZcbk*jWh{N;)f_*}XlIND@*;o`wJJ&dl@rn`w^QaPmQOo=;CqF}%(( z_WaYmXzS-VgEuExVL-d-!>%OAZI&Nqg|S^ge^F;`KZRHZ7M}R)q4kZ9e9$`66WSae zg)E2w5jZk|!nVezhuQV1Ym?4*bmR)ZaQ^<0!;k|JAOb{y2oM1xKm>>Y5g-D`mw?TO z*b6NGM8Ad0{pZl0XMac!B0vO)01+SpM1Tko0U|&IhyW2F0z}{#5(r|(zMWz$IgQ2R z|Nr&x{~u==`-=L4`iS~~dX0LC>YyH?{+wa#GinR<7y({TuZQ z^&RyN^$PV2rBMpy^1nsVMFfZd5g-CYfCvx)B0vO)!2dy@$2fPY@0_P#Owa~Vzf4GF zGp{jL8ZAN65@NnG;a!}|i;%ZeZR`!aWtu0viLN)6td^=h+u-A~{Eqvi82!rCdcdjz zeE%*e&@yT+lq;UGu@Ev7q8#=s804-~3SK4Py{g2~6{CVy;lF!8D7*F)!nGimLf~!972hvv-=y8% zs;a~C^_zo8d~UH>)q-VInd&NbXW!lYfqC|ZkFqo^l#Bu^Gqq2$W*R3udX+d7qGD9x zJ}~2Qg~d;UwgSsF1Tk(g$F0o+ZQN@a>Yo;GWvV6HJMvZ&<^CR+u(B(3)ly3)uy0cC z82}!H*#mr`8>iBL5QeqREQn@%`oQs&ZIn^LR_6G9r~kOLsG=(iPL`p Dbr58f literal 0 HcmV?d00001 diff --git a/src/action/.add.rs.swp b/src/action/.add.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..4ccaed1b5ee3d88a73d55ab8cb7b1fc4bbe33362 GIT binary patch literal 12288 zcmeI2J#W)M7{{+LAzF$6F<(>EI#L=JSSklvg;FGh5LFQ`QU#G?pOd40>-oZqP+(zZ zWMx4@Oo%VQ#=yqXjfs_R{Q~^YcI1Sm>R9PH>7TQoyL(=KcZ-$lEg2oQmz5E$rSX=R38y>fZkESs;*@$=^%9K{38B?3f%2oM1xKm>>Y5g-CY z;FuH8=@fg58cvjT@Uox!uP>Y5g-CYfCvx)B0vO=ApzHC?DRBa zpCR-AKQC+8pJnVX^b7h9eT3dY9q1{v0$qfDpJD6=^a*+fJ%%1ZYtSWV7Mg*kq5T=g zzCjtZ1+}3I&?#sS@xMY}%3MC67f=jQBLYN#2oM1xKm>>Y5g-CY;204YGLBaKnx}g^ zmQ}Aa^JrSCWURw@Zdf&D?B#;oI&Q#_o608c3R?@2+lz&6@Jxh3Gj(yp*smOiLYtZV zI&Y_PJ(MEWjuWUP>Uc>U$ey)O^!2q??jMf%uzEDFI#CMCIy&hTp{#{GRB5kb4Y>$2 z!Cy18YShN3HoY*3g;lLE*%dyKpbUxVs-%v59BHrIVI;(?xne?Bz7?W$hiN|Q{QJKSzqe}eBwv|>9P-c6FQ@{(I8e91V| z!u5Fq2BvHT_V@4-O+!AVX1SWl7nf>m^a-#(smxZX^(}28T6-oZt3^lFx;83&*LgdRc>Sn$+EJ zW4FO?iteCiFyK6LPvNG!;|#P6Gsh_dRR^jyKB?SFr`XAfq2h+T<5U|sF`V!6gas!b SvquNNRU~7!%5g?Xx7a^|@K#U& literal 0 HcmV?d00001 diff --git a/src/action/add.rs b/src/action/add.rs index b2c6678..42f73d5 100644 --- a/src/action/add.rs +++ b/src/action/add.rs @@ -1,22 +1,32 @@ use argh::FromArgs; -use tokio::task; -use qbittorrent_web_api::Api; +use crate::api::ApiClient; use crate::Error; use crate::action::ActionExec; use crate::config::Config; +use crate::utils::torrent_to_magnet; #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "add")] -/// add a torrent to qBittorrent (only magnet for the moment) +/// add a torrent to qBittorrent (magnet or torrent file) pub struct AddAction { + #[argh(switch, short = 'p')] + /// pause the torrent instead of starting immediately + paused: bool, + #[argh(positional)] - /// the magnet link to add - magnet: String, + /// the torrent to add + torrent: String, } impl ActionExec for AddAction { - fn exec(&self, _config: &Config) -> Result<(), Error> { - unimplemented!(); + fn exec(&self, config: &Config) -> Result<(), Error> { + let magnet = if self.torrent.starts_with("magnet:") { + self.torrent.clone() + } else { + torrent_to_magnet(&self.torrent) + }; + let api = ApiClient::from_config(&config)?; + api.add(&magnet, self.paused) } } diff --git a/src/api.rs b/src/api.rs index 2c0b391..ee1dba8 100644 --- a/src/api.rs +++ b/src/api.rs @@ -27,6 +27,21 @@ impl ApiClient { }) } + pub fn add(&self, magnet: &str, paused: bool) -> Result<(), Error> { + let base_call = self.api.torrent_management(); + let call = if paused { + base_call.add(magnet).paused("true") + } else { + base_call.add(magnet) + }; + let res = self.rt.block_on(call.send_raw()).context(ApiError)?; + if res == "Ok." { + Ok(()) + } else { + Err(Error::message(res.clone())) + } + } + pub fn get(&self, hash: &str) -> Result, Error> { let res = self.rt.block_on(self.api.torrent_management().properties_raw(hash)).context(ApiError)?; if res == "" { diff --git a/src/cli.rs b/src/cli.rs index d6118f4..35b8bf9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,10 +1,16 @@ use argh::FromArgs; +use std::path::{PathBuf}; + use crate::action::Action; #[derive(FromArgs, Debug)] /// interact with your qBittorrent instance from the command line pub struct Cli { + #[argh(option, short = 'c')] + /// path to config file (defaults to ~/.config/qbt/.qbt.toml) + pub config: Option, + #[argh(subcommand)] pub command: Action, } diff --git a/src/config.rs b/src/config.rs index 96fc401..0fbd51a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,8 @@ use xdg::{BaseDirectories, BaseDirectoriesError}; use std::path::{Path, PathBuf}; +use crate::cli::Cli; + #[derive(Deserialize, Debug)] pub struct Config { pub qbittorrent: ClientConfig, @@ -46,7 +48,15 @@ impl Config { } pub fn from_default_path() -> Result { - Ok(Self::from_path(&Self::default_path()?)?) + Self::from_path(&Self::default_path()?) + } + + pub fn from_cli(cli: &Cli) -> Result { + if let Some(cfg_path) = &cli.config { + Self::from_path(cfg_path) + } else { + Self::from_default_path() + } } pub fn format_host(&self) -> String { diff --git a/src/error.rs b/src/error.rs index dc1fed6..2bca881 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,13 +7,23 @@ use crate::config::ConfigError; #[snafu(visibility(pub))] /// The possible error cases pub enum Error { - #[snafu(display("Configuration file reading or parsing error"))] + #[snafu(display("{}\nConfiguration file reading or parsing error (see above)", source))] Config { source: ConfigError }, - #[snafu(display("Torrent/magnet reading or parsing error"))] + #[snafu(display("{}\nTorrent/magnet reading or parsing error", source))] Imdl { source: imdl::error::Error }, - #[snafu(display("qBittorrent API communication error"))] + #[snafu(display("{}\nqBittorrent API communication error", source))] Api { source: std::io::Error }, - #[snafu(display("Internal qBittorrent API error"))] + #[snafu(display("{}\nInternal qBittorrent API error", source))] InternalApi { source: qbittorrent_web_api::api_impl::Error }, + #[snafu(display("Other error:\n{}", message))] + Message { message: String }, //GenericIOError(std::io::Error), } + +impl Error { + pub fn message(message: impl Into) -> Error { + Self::Message { + message: message.into(), + } + } +} diff --git a/src/main.rs b/src/main.rs index 1b20b41..13228c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,20 @@ mod error; use crate::error::{Error, ConfigSnafu as ConfigError}; mod utils; -fn main() -> Result<(), Error> { - let config = Config::from_default_path().context(ConfigError)?; +fn fallible_main() -> Result<(), Error> { let cli = Cli::from_args(); + let config = Config::from_cli(&cli).context(ConfigError)?; // TODO: Make ActionExec type return Option? where None means no data was found and so we abort with a proper exit code cli.command.exec(&config)?; Ok(()) } + +fn main() { + let res = fallible_main(); + if res.is_err() { + let err = res.unwrap_err(); + eprintln!("{}", err); + std::process::exit(1); + } +} diff --git a/src/utils.rs b/src/utils.rs index 76bf19e..2d186b9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -20,6 +20,7 @@ pub fn input_path>(path: T) -> Result { /// Extracts the infohash of a magnet link pub fn magnet_hash>(magnet: T) -> String { + // TODO: error let magnet = MagnetLink::parse(magnet.as_ref()).expect("Parsing magnet failed"); //Ok(magnet.name.expect("Magnet link has no name!")) //Ok(String::from_utf8_lossy(&magnet.infohash.inner.bytes).to_string()) @@ -33,12 +34,14 @@ pub fn magnet_hash>(magnet: T) -> String { /// Extracts the name of a magnet link pub fn magnet_name>(magnet: T) -> String { + // TODO: error let magnet = MagnetLink::parse(magnet.as_ref()).expect("Parsing magnet failed"); magnet.name.expect("Magnet link has no name!") } /// Extracts the infohash of a torrent file pub fn torrent_hash>(torrent: T) -> String { + // TODO: error let input = input_path(torrent).unwrap(); TorrentSummary::from_input(&input).expect("Parsing torrent file failed").torrent_summary_data().info_hash }