Load settings from -c/--config flag and print errors

Ignore hidden files
Add add subcommand
This commit is contained in:
programmer programmer 2022-10-16 15:50:02 +02:00
parent 9097a18926
commit cbe7bc51a6
10 changed files with 78 additions and 14 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
.*

BIN
src/.error.rs.swp Normal file

Binary file not shown.

BIN
src/action/.add.rs.swp Normal file

Binary file not shown.

View File

@ -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)
}
}

View File

@ -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<Option<String>, Error> {
let res = self.rt.block_on(self.api.torrent_management().properties_raw(hash)).context(ApiError)?;
if res == "" {

View File

@ -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<PathBuf>,
#[argh(subcommand)]
pub command: Action,
}

View File

@ -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<Config, ConfigError> {
Ok(Self::from_path(&Self::default_path()?)?)
Self::from_path(&Self::default_path()?)
}
pub fn from_cli(cli: &Cli) -> Result<Config, ConfigError> {
if let Some(cfg_path) = &cli.config {
Self::from_path(cfg_path)
} else {
Self::from_default_path()
}
}
pub fn format_host(&self) -> String {

View File

@ -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<String>) -> Error {
Self::Message {
message: message.into(),
}
}
}

View File

@ -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<String>? 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);
}
}

View File

@ -20,6 +20,7 @@ pub fn input_path<T: AsRef<Path>>(path: T) -> Result<Input, std::io::Error> {
/// Extracts the infohash of a magnet link
pub fn magnet_hash<T: AsRef<str>>(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<T: AsRef<str>>(magnet: T) -> String {
/// Extracts the name of a magnet link
pub fn magnet_name<T: AsRef<str>>(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<T: AsRef<Path>>(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
}