From 01c9984d9935dbd1f72acd0ed52bac658918249c Mon Sep 17 00:00:00 2001 From: askiiart Date: Sun, 1 Dec 2024 22:00:19 -0600 Subject: [PATCH] switch from `static mut` config to using Rocket's State --- Cargo.lock | 1 - Cargo.toml | 1 - src/api.rs | 159 ++++++++++++++------------------------------- src/dummy.rs | 17 ----- src/lib.rs | 16 +++-- src/notes/usage.rs | 2 +- 6 files changed, 61 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee58864..5476d95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,7 +1277,6 @@ name = "torznab-toolkit" version = "0.1.0" dependencies = [ "actix-rt", - "lazy_static", "rocket", "serde", "xml-rs", diff --git a/Cargo.toml b/Cargo.toml index b404c62..ca7651b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] actix-rt = "2.10.0" -lazy_static = "1.5.0" rocket = "0.5.1" serde = { version = "1.0.215", features = ["derive"] } xml-rs = "0.8.23" diff --git a/src/api.rs b/src/api.rs index 216917f..432e157 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,10 +1,9 @@ //! Contains the actual Torznab API use crate::data::*; -use lazy_static::lazy_static; use rocket::http::Status; use rocket::response::status; -use rocket::FromForm; use rocket::{get, response::content::RawXml}; +use rocket::{FromForm, State}; use std::str; use xml::writer::{EmitterConfig, XmlEvent}; @@ -81,41 +80,11 @@ impl SearchForm { } } -// Holds the config for torznab-toolkit. -// -// A search function (`/api?t=search`) and capabilities (`/api?t=caps` - `Caps`) are required, everything else is optional. -// -//
It's required to be set to something, which is why it's an Option set to None. -// -// However, this is NOT optional, and attempting to do anything with CONFIG not set will return an `Err`.
- -pub(crate) static mut CONFIG: Option = None; -lazy_static! { - static ref STATUS_CONFIG_NOT_SPECIFIED: status::Custom> = status::Custom( - Status::InternalServerError, - RawXml("500 Internal server error: Config not specified".to_string()), - ); -} - /// Capabilities API endpoint (`/api?t=caps`) /// /// Note that an apikey is *not* required for this function, regardless of whether it's required for the rest. -#[get("/api?t=caps")] -pub(crate) async fn caps() -> status::Custom> { - // The compiler won't let you get a field from a struct in the Option here, since the default is None - // So this is needed - let conf; - unsafe { - match CONFIG { - Some(ref config) => { - conf = config.clone(); - } - None => { - return (*STATUS_CONFIG_NOT_SPECIFIED).clone(); - } - } - } - +#[get("/api?t=caps", rank = 1)] +pub(crate) async fn caps(conf: &State) -> status::Custom> { let buffer = Vec::new(); let mut writer = EmitterConfig::new().create_writer(buffer); @@ -148,7 +117,7 @@ pub(crate) async fn caps() -> status::Custom> { // Add the search types writer.write(XmlEvent::start_element("searching")).unwrap(); - for item in conf.caps.searching { + for item in &conf.caps.searching { let mut available = "yes"; if !item.available { available = "no"; @@ -165,7 +134,7 @@ pub(crate) async fn caps() -> status::Custom> { writer.write(XmlEvent::end_element()).unwrap(); // close `searching` writer.write(XmlEvent::start_element("categories")).unwrap(); - for i in conf.caps.categories { + for i in &conf.caps.categories { writer .write( XmlEvent::start_element("category") @@ -173,7 +142,7 @@ pub(crate) async fn caps() -> status::Custom> { .attr("name", i.name.as_str()), ) .unwrap(); - for j in i.subcategories { + for j in &i.subcategories { writer .write( XmlEvent::start_element("subcat") @@ -187,7 +156,7 @@ pub(crate) async fn caps() -> status::Custom> { } writer.write(XmlEvent::end_element()).unwrap(); // close `categories` - match conf.caps.genres { + match &conf.caps.genres { Some(genres) => { writer.write(XmlEvent::start_element("genres")).unwrap(); @@ -207,7 +176,7 @@ pub(crate) async fn caps() -> status::Custom> { None => {} } - match conf.caps.tags { + match &conf.caps.tags { Some(tags) => { writer.write(XmlEvent::start_element("tags")).unwrap(); @@ -233,21 +202,14 @@ pub(crate) async fn caps() -> status::Custom> { return status::Custom(Status::Ok, RawXml(result)); } -#[get("/api?t=search&")] +#[get("/api?t=search&", rank = 2)] /// The general search function -pub(crate) async fn search(form: SearchForm) -> status::Custom> { - // The compiler won't let you get a field from a struct in the Option here, since the default is None - // So this is needed - let conf; - unsafe { - if CONFIG.is_none() { - return (*STATUS_CONFIG_NOT_SPECIFIED).clone(); - } else { - conf = CONFIG.clone().ok_or("").unwrap(); - } - } - - let parameters = form.to_parameters(conf.clone()); +pub(crate) async fn search( + conf: &State, + form: SearchForm, +) -> status::Custom> { + // oh god this is horrible but it works + let parameters = form.to_parameters((**conf).clone()); let mut unauthorized = false; match conf.auth { @@ -276,21 +238,14 @@ pub(crate) async fn search(form: SearchForm) -> status::Custom> { return search_handler(conf, search_parameters).await; } -#[get("/api?t=tvsearch&")] +#[get("/api?t=tvsearch&", rank = 3)] /// The TV search function -pub(crate) async fn tv_search(form: SearchForm) -> status::Custom> { - // The compiler won't let you get a field from a struct in the Option here, since the default is None - // So this is needed - let conf; - unsafe { - if CONFIG.is_none() { - return (*STATUS_CONFIG_NOT_SPECIFIED).clone(); - } else { - conf = CONFIG.clone().ok_or("").unwrap(); - } - } - - let parameters = form.to_parameters(conf.clone()); +pub(crate) async fn tv_search( + conf: &State, + form: SearchForm, +) -> status::Custom> { + // oh god this is horrible but it works + let parameters = form.to_parameters((**conf).clone()); let mut unauthorized = false; match conf.auth { @@ -326,21 +281,14 @@ pub(crate) async fn tv_search(form: SearchForm) -> status::Custom return search_handler(conf, search_parameters).await; } -#[get("/api?t=movie&")] +#[get("/api?t=movie&", rank = 4)] /// The movie search function -pub(crate) async fn movie_search(form: SearchForm) -> status::Custom> { - // The compiler won't let you get a field from a struct in the Option here, since the default is None - // So this is needed - let conf; - unsafe { - if CONFIG.is_none() { - return (*STATUS_CONFIG_NOT_SPECIFIED).clone(); - } else { - conf = CONFIG.clone().ok_or("").unwrap(); - } - } - - let parameters = form.to_parameters(conf.clone()); +pub(crate) async fn movie_search( + conf: &State, + form: SearchForm, +) -> status::Custom> { + // oh god this is horrible but it works + let parameters = form.to_parameters((**conf).clone()); let mut unauthorized = false; match conf.auth { @@ -376,21 +324,14 @@ pub(crate) async fn movie_search(form: SearchForm) -> status::Custom")] +#[get("/api?t=music&", rank = 5)] /// The music search function -pub(crate) async fn music_search(form: SearchForm) -> status::Custom> { - // The compiler won't let you get a field from a struct in the Option here, since the default is None - // So this is needed - let conf; - unsafe { - if CONFIG.is_none() { - return (*STATUS_CONFIG_NOT_SPECIFIED).clone(); - } else { - conf = CONFIG.clone().ok_or("").unwrap(); - } - } - - let parameters = form.to_parameters(conf.clone()); +pub(crate) async fn music_search( + conf: &State, + form: SearchForm, +) -> status::Custom> { + // oh god this is horrible but it works + let parameters = form.to_parameters((**conf).clone()); let mut unauthorized = false; match conf.auth { @@ -426,21 +367,14 @@ pub(crate) async fn music_search(form: SearchForm) -> status::Custom")] +#[get("/api?t=book&", rank = 6)] /// The music search function -pub(crate) async fn book_search(form: SearchForm) -> status::Custom> { - // The compiler won't let you get a field from a struct in the Option here, since the default is None - // So this is needed - let conf; - unsafe { - if CONFIG.is_none() { - return (*STATUS_CONFIG_NOT_SPECIFIED).clone(); - } else { - conf = CONFIG.clone().ok_or("").unwrap(); - } - } - - let parameters = form.to_parameters(conf.clone()); +pub(crate) async fn book_search( + conf: &State, + form: SearchForm, +) -> status::Custom> { + // oh god this is horrible but it works + let parameters = form.to_parameters((**conf).clone()); let mut unauthorized = false; match conf.auth { @@ -476,7 +410,10 @@ pub(crate) async fn book_search(form: SearchForm) -> status::Custom status::Custom> { +async fn search_handler( + conf: &State, + parameters: SearchParameters, +) -> status::Custom> { let buffer = Vec::new(); let mut writer = EmitterConfig::new().create_writer(buffer); writer @@ -499,7 +436,7 @@ async fn search_handler(conf: Config, parameters: SearchParameters) -> status::C // add `title` writer.write(XmlEvent::start_element("title")).unwrap(); let mut title_provided = false; - match conf.caps.server_info { + match &conf.caps.server_info { Some(server_info) => { if server_info.contains_key("title") { match server_info.get("title") { diff --git a/src/dummy.rs b/src/dummy.rs index 2d704af..95d02dd 100644 --- a/src/dummy.rs +++ b/src/dummy.rs @@ -91,23 +91,6 @@ pub(crate) fn create_empty_config() -> Config { mod tests { use crate::{api, dummy::create_empty_config, run}; - #[actix_rt::test] - async fn caps_test_with_empty_config() { - unsafe { - crate::api::CONFIG = Some(create_empty_config()); - println!("{:?}", crate::api::CONFIG); - } - println!("{:?}", crate::api::caps().await); - } - - #[actix_rt::test] - async fn caps_test_no_config() { - unsafe { - println!("{:?}", crate::api::CONFIG); - } - println!("{:?}", crate::api::caps().await); - } - #[actix_rt::test] async fn api_with_empty_config() { run(create_empty_config()).await.unwrap(); diff --git a/src/lib.rs b/src/lib.rs index e303197..bfa46bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,11 +8,19 @@ use rocket::{self}; /// Runs the server pub async fn run(conf: data::Config) -> Result { - unsafe { - api::CONFIG = Some(conf); - } match rocket::build() - .mount("/", rocket::routes![api::caps, api::search]) + .mount( + "/", + rocket::routes![ + api::caps, + api::search, + api::tv_search, + api::movie_search, + api::music_search, + api::book_search + ], + ) + .manage(conf) .launch() .await { diff --git a/src/notes/usage.rs b/src/notes/usage.rs index 2f1cc5a..05334eb 100644 --- a/src/notes/usage.rs +++ b/src/notes/usage.rs @@ -5,7 +5,7 @@ //! - Please implement the `season`, `ep`, and `id` attributes for torrents when possible //! - Implementing `id`, at least, is far out of scope of this library, and providing `season` and `ep` more effective than this library parsing for them. However, parsing for those as an optional fallback may be added later. //! - See [here](https://torznab.github.io/spec-1.3-draft/revisions/1.0-Torznab-Torrent-Support.html) for details -//! +//! //! TODO: Add better docs for using the library // TODO: Add parsing for `season` and `ep`