Start working on reading config from env, cli, and toml
This commit is contained in:
parent
0de10cbffe
commit
5426c6186e
|
@ -0,0 +1,7 @@
|
|||
/target
|
||||
.vscode
|
||||
|
||||
# Debug related directories that we don't want included
|
||||
/torrents
|
||||
/output
|
||||
config.toml
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "cross-seed"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.19.2", features = ["full"] }
|
||||
toml = "0.5.9"
|
||||
lava_torrent = "0.7.0" # https://docs.rs/lava_torrent/0.7.0/lava_torrent/
|
||||
torznab = "0.7.2" # https://docs.rs/torznab/0.7.2/torznab/
|
||||
magnet-url = "2.0.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
figment = { version = "0.10", features = ["toml", "env"] }
|
||||
wild = "2.0.4"
|
||||
argmap = "1.1.2"
|
|
@ -0,0 +1,94 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use figment::{Provider, Metadata, Profile, Error};
|
||||
use figment::value::{Map, Dict, Value, Tag};
|
||||
use serde::Deserialize;
|
||||
|
||||
/// A provider that fetches its data from a given URL.
|
||||
pub struct CliProvider {
|
||||
/// The profile to emit data to if nesting is disabled.
|
||||
profile: Option<Profile>,
|
||||
args: Vec<std::string::String>,
|
||||
}
|
||||
|
||||
impl CliProvider {
|
||||
pub fn new() -> CliProvider {
|
||||
CliProvider {
|
||||
profile: None,
|
||||
args: wild::args().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Provider for CliProvider {
|
||||
/// Returns metadata with kind `Network`, custom source `self.url`,
|
||||
/// and interpolator that returns a URL of `url/a/b/c` for key `a.b.c`.
|
||||
fn metadata(&self) -> Metadata {
|
||||
let args = &self.args;
|
||||
Metadata::named("CLI Flags")
|
||||
.source(args.join(" "))
|
||||
//.source(args.map(|args| args.collect::<Vec<_>>().join(" ")).unwrap_or(String::default()))
|
||||
/* .interpolater(move |profile, keys| match profile.is_custom() {
|
||||
true => format!("{}/{}/{}", url, profile, keys.join("/")),
|
||||
false => format!("{}/{}", url, keys.join("/")),
|
||||
}) */
|
||||
}
|
||||
|
||||
/// Fetches the data from `self.url`. Note that `Dict`, `Map`, and
|
||||
/// `Profile` are `Deserialize`, so we can deserialized to them.
|
||||
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
|
||||
// Parse a `Value` from a `String`
|
||||
fn parse_from_string(string: &String) -> Value {
|
||||
// TODO: Other integer types
|
||||
match string.parse::<i32>() {
|
||||
Ok(i) => Value::Num(Tag::Default, figment::value::Num::I32(i)),
|
||||
Err(_) => match string.parse::<bool>() {
|
||||
Ok(b) => Value::Bool(Tag::Default, b),
|
||||
Err(_) => Value::from(string.to_owned()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch<'a, T: Deserialize<'a>>(args: &Vec<std::string::String>) -> Result<T, Error> {
|
||||
let (args, argv) = argmap::parse(args.iter());
|
||||
|
||||
let mut dict = Dict::new();
|
||||
|
||||
for (key, vals) in argv {
|
||||
let len = vals.len();
|
||||
if len == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let key_vec: Vec<&str> = key.split(".").collect();
|
||||
for key in key_vec.iter() {
|
||||
dict.insert(key.to_owned(), Value::from(key.to_owned()));
|
||||
}
|
||||
|
||||
if len == 1 {
|
||||
dict.insert(key, parse_from_string(&vals[0]));
|
||||
} else {
|
||||
let mut values = Vec::new();
|
||||
for val in &vals {
|
||||
values.push(parse_from_string(val));
|
||||
}
|
||||
|
||||
dict.insert(key, Value::Array(Tag::Default, values));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(T::deserialize(dict).unwrap())
|
||||
|
||||
//Ok(T::deserialize(args.unwrap_or(&std::env::args()))?)
|
||||
|
||||
//Profile::default()
|
||||
}
|
||||
|
||||
match &self.profile {
|
||||
// Don't nest: `fetch` into a `Dict`.
|
||||
Some(profile) => Ok(profile.collect(fetch(&self.args)?)),
|
||||
// Nest: `fetch` into a `Map<Profile, Dict>`.
|
||||
None => fetch(&self.args),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
use serde::{Deserialize,Serialize};
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
use std::collections::HashMap;
|
||||
use figment::{Figment, providers::{Format, Toml, Env}};
|
||||
use figment::value::Value as FigmentValue;
|
||||
|
||||
use super::CliProvider;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
/// The path of the torrents to search.
|
||||
torrents_path: String,
|
||||
/// The output path of the torrents.
|
||||
output_path: Option<String>,
|
||||
|
||||
//pub indexers: HashMap<String, Indexer>,
|
||||
|
||||
|
||||
/// Used for deserializing the indexers into a Vec<Indexer>.
|
||||
#[serde(rename = "indexers")]
|
||||
indexers_map: HashMap<String, FigmentValue>,
|
||||
|
||||
/// The indexers to search.
|
||||
#[serde(skip)]
|
||||
pub indexers: Vec<Indexer>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Indexer {
|
||||
#[serde(skip_deserializing)]
|
||||
pub name: String,
|
||||
pub enabled: Option<bool>,
|
||||
pub url: String,
|
||||
pub api_key: String,
|
||||
}
|
||||
|
||||
// Allow dead code for functions. We should probably remove this later on.
|
||||
#[allow(dead_code)]
|
||||
impl Config {
|
||||
pub fn new() -> Config {
|
||||
// The path of the config file without the file extension
|
||||
let path = match env::var("CROSS_SEED_CONFIG") {
|
||||
Ok(path) => path,
|
||||
Err(_) => "config".to_string(),
|
||||
};
|
||||
|
||||
// TODO: Create a command line argument `Provider` (https://docs.rs/figment/0.10.6/figment/trait.Provider.html)
|
||||
// TODO: Figure out priority
|
||||
// Merge the config files
|
||||
let figment = Figment::new()
|
||||
.join(Toml::file(format!("{}.toml", path)))
|
||||
.join(Env::prefixed("CROSS_SEED_"))
|
||||
.join(CliProvider::new());
|
||||
|
||||
let mut config: Config = figment.extract().unwrap();
|
||||
|
||||
// Parse the indexers map into a vector.
|
||||
for (name, value) in &mut config.indexers_map {
|
||||
let mut indexer: Indexer = value.deserialize().unwrap();
|
||||
indexer.name = name.to_owned();
|
||||
|
||||
config.indexers.push(indexer);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
pub fn torrents_path(&self) -> &Path {
|
||||
Path::new(&self.torrents_path)
|
||||
}
|
||||
|
||||
pub fn torrents_path_str(&self) -> &String {
|
||||
&self.torrents_path
|
||||
}
|
||||
|
||||
pub fn output_path(&self) -> Option<&Path> {
|
||||
match self.output_path {
|
||||
Some(ref path) => Some(Path::new(path)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_path_str(&self) -> Option<&String> {
|
||||
self.output_path.as_ref()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pub mod config;
|
||||
pub use config::Config;
|
||||
|
||||
pub mod cli_provider;
|
||||
pub use cli_provider::CliProvider;
|
|
@ -0,0 +1,75 @@
|
|||
mod config;
|
||||
|
||||
use config::Config;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::error::Error;
|
||||
|
||||
use lava_torrent::torrent::v1::Torrent;
|
||||
|
||||
use torznab::Client as TorznabClient;
|
||||
|
||||
fn read_torrents(path: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||
let mut torrents = Vec::new();
|
||||
for entry in path.read_dir()? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let filename = path.file_name().unwrap().to_str().unwrap();
|
||||
if filename.ends_with(".torrent") {
|
||||
torrents.push(path);
|
||||
}
|
||||
} else {
|
||||
let mut inner = read_torrents(&path)?;
|
||||
torrents.append(&mut inner);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(torrents);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Get config and debug the torrents
|
||||
let config = Config::new();//.expect("Failed to get config");
|
||||
println!("Searching torrents in: {}", config.torrents_path_str());
|
||||
|
||||
println!("Searching {} trackers: ", config.indexers.len());
|
||||
for indexer in config.indexers.iter() {
|
||||
println!(" {}: {}", indexer.name, indexer.url);
|
||||
}
|
||||
|
||||
let torrents = read_torrents(config.torrents_path()).unwrap();
|
||||
|
||||
for torrent_path in torrents.iter() {
|
||||
let torrent = Torrent::read_from_file(torrent_path).unwrap();
|
||||
println!("{}:", torrent.name);
|
||||
|
||||
/* for indexer in config.indexers.iter() {
|
||||
if indexer.enabled {
|
||||
let client = TorznabClient::new(indexer.url.clone());
|
||||
let results = client.search(&torrent).unwrap();
|
||||
println!("{}", results);
|
||||
}
|
||||
} */
|
||||
//TorznabClient
|
||||
|
||||
/*if let Some(announce) = torrent.announce {
|
||||
println!(" Announce: {}", announce);
|
||||
}
|
||||
if let Some(announce_list) = torrent.announce_list {
|
||||
println!(" Announce list:");
|
||||
for announce in announce_list {
|
||||
for ann in announce {
|
||||
println!(" {}", ann);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!(" Files:");
|
||||
|
||||
if let Some(files) = torrent.files {
|
||||
for file in files.iter() {
|
||||
println!(" {}", file.path.to_str().unwrap());
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue