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`