diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..e70e158 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,30 @@ +use crate::{Meta, Scrape, Scraper, Torrent}; +use rocket::{State, serde::Serialize}; + +#[derive(Serialize)] +#[serde(crate = "rocket::serde")] +pub struct Format { + pub created: Option, + pub files: String, + pub indexed: String, + pub magnet: String, + pub scrape: Option, + pub size: String, + pub torrent: Torrent, +} + +impl Format { + pub fn from_torrent(torrent: Torrent, scraper: &State, meta: &State) -> Self { + Self { + created: torrent + .creation_date + .map(|t| t.format(&meta.format_time).to_string()), + indexed: torrent.time.format(&meta.format_time).to_string(), + magnet: torrent.magnet(meta.trackers.as_ref()), + scrape: scraper.scrape(&torrent.info_hash), + size: torrent.size(), + files: torrent.files(), + torrent, + } + } +} diff --git a/src/main.rs b/src/main.rs index 7f3cc91..23db5d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,36 +3,27 @@ extern crate rocket; mod config; mod feed; +mod format; +mod meta; mod scraper; mod storage; mod torrent; use config::Config; use feed::Feed; +use format::Format; +use meta::Meta; use plurify::Plurify; use rocket::{ State, http::Status, response::{content::RawXml, status::Custom}, - serde::Serialize, }; use rocket_dyn_templates::{Template, context}; use scraper::{Scrape, Scraper}; +use std::str::FromStr; use storage::{Order, Sort, Storage}; use torrent::Torrent; -use url::Url; - -#[derive(Clone, Debug, Serialize)] -#[serde(crate = "rocket::serde")] -pub struct Meta { - pub canonical: Option, - pub description: Option, - pub format_time: String, - pub title: String, - /// * use vector to keep the order from the arguments list - pub trackers: Option>, - pub version: String, -} #[get("/?")] fn index( @@ -41,18 +32,6 @@ fn index( storage: &State, meta: &State, ) -> Result> { - #[derive(Serialize)] - #[serde(crate = "rocket::serde")] - struct Row { - created: Option, - files: String, - indexed: String, - magnet: String, - scrape: Option, - size: String, - torrent: Torrent, - } - let (total, torrents) = storage .torrents( Some((Sort::Modified, Order::Desc)), @@ -74,23 +53,13 @@ fn index( rows: torrents .into_iter() .filter_map(|t| match Torrent::from_storage(&t.bytes, t.time) { - Ok(torrent) => Some(Row { - created: torrent - .creation_date - .map(|t| t.format(&meta.format_time).to_string()), - indexed: torrent.time.format(&meta.format_time).to_string(), - magnet: torrent.magnet(meta.trackers.as_ref()), - scrape: scraper.scrape(&torrent.info_hash), - size: torrent.size(), - files: torrent.files(), - torrent, - }), + Ok(torrent) => Some(Format::from_torrent(torrent, scraper, meta)), Err(e) => { error!("Torrent storage read error: `{e}`"); None } }) - .collect::>(), + .collect::>(), pagination_totals: format!( "Page {} / {} ({total} {} total)", page.unwrap_or(1), @@ -101,6 +70,34 @@ fn index( )) } +#[get("/")] +fn info( + info_hash: &str, + storage: &State, + scraper: &State, + meta: &State, +) -> Result> { + match storage.torrent(librqbit_core::Id20::from_str(info_hash).map_err(|e| { + warn!("Torrent info-hash parse error: `{e}`"); + Custom(Status::BadRequest, Status::BadRequest.to_string()) + })?) { + Some(t) => Ok(Template::render( + "info", + context! { + meta: meta.inner(), + torrent: Format::from_torrent( + Torrent::from_storage(&t.bytes, t.time).map_err(|e| { + error!("Torrent parse error: `{e}`"); + Custom(Status::InternalServerError, E.to_string()) + })?, scraper, meta + ), + info_hash + }, + )), + None => Err(Custom(Status::NotFound, E.to_string())), + } +} + #[get("/rss")] fn rss(feed: &State, storage: &State) -> Result, Custom> { let mut b = feed.transaction(1024); // @TODO @@ -189,7 +186,7 @@ fn rocket() -> _ { version: env!("CARGO_PKG_VERSION").into(), }) .mount("/", rocket::fs::FileServer::from(config.statics)) - .mount("/", routes![index, rss]) + .mount("/", routes![index, info, rss]) } /// Public placeholder text for the `Status::InternalServerError` diff --git a/src/meta.rs b/src/meta.rs new file mode 100644 index 0000000..3fa82b3 --- /dev/null +++ b/src/meta.rs @@ -0,0 +1,14 @@ +use rocket::serde::Serialize; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct Meta { + pub canonical: Option, + pub description: Option, + pub format_time: String, + pub title: String, + /// * use vector to keep the order from the arguments list + pub trackers: Option>, + pub version: String, +} diff --git a/src/storage.rs b/src/storage.rs index 7a54274..db2add9 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -5,6 +5,8 @@ use std::{ path::PathBuf, }; +const EXTENSION: &str = "torrent"; + #[derive(Clone, Debug, Default)] pub enum Sort { #[default] @@ -49,6 +51,15 @@ impl Storage { // Getters + pub fn torrent(&self, info_hash: librqbit_core::Id20) -> Option { + let mut p = PathBuf::from(&self.root); + p.push(format!("{}.{EXTENSION}", info_hash.as_string())); + Some(Torrent { + bytes: fs::read(&p).ok()?, + time: p.metadata().ok()?.modified().ok()?.into(), + }) + } + pub fn torrents( &self, sort_order: Option<(Sort, Order)>, @@ -66,7 +77,7 @@ impl Storage { .filter(|f| { f.path() .extension() - .is_some_and(|e| !e.is_empty() && e.to_string_lossy() == "torrent") + .is_some_and(|e| !e.is_empty() && e.to_string_lossy() == EXTENSION) }) { b.push(Torrent { diff --git a/static/theme/default.css b/static/theme/default.css index efe858b..25c705f 100644 --- a/static/theme/default.css +++ b/static/theme/default.css @@ -43,12 +43,7 @@ h1, h2, h3, h4, h5 { font-weight: normal; } -h1 { - font-size: 16px; -} - -h2 { - color: var(--default); +h1, h2 { font-size: 14px; } diff --git a/templates/index.html.tera b/templates/index.html.tera index 218e8b9..5e20cf5 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -4,7 +4,7 @@ {% for row in rows %}
-

{{ row.torrent.name }}

+

{{ row.torrent.name }}

{% if row.torrent.comment %}

{{ row.torrent.comment }}

{% endif %}
    diff --git a/templates/info.html.tera b/templates/info.html.tera new file mode 100644 index 0000000..ea82892 --- /dev/null +++ b/templates/info.html.tera @@ -0,0 +1,27 @@ +{% extends "layout/default" %} +{% block content %} + {% if torrent %} +
    +

    {% if torrent.name %}{{ torrent.name }}{% else %}{{ info_hash }}{% endif %}

    + {% if torrent.comment %}

    {{ torrent.comment }}

    {% endif %} +
    +
      +
    • {{ torrent.indexed }}
    • + {% if torrent.created %}
    • ({{ torrent.created }})
    • {% endif %} + {% if torrent.size %}
    • {{ torrent.size }}
    • {% endif %} +
    • {{ torrent.files }}
    • + {% if torrent.scrape %} +
    • {{ torrent.scrape.seeders }}
    • +
    • {{ torrent.scrape.peers }}
    • +
    • {{ torrent.scrape.leechers }}
    • + {% endif %} +
    +
    + +
    +
    +
    + {% else %} +
    Nothing.
    + {% endif %} +{% endblock content %} \ No newline at end of file