implement files list

This commit is contained in:
yggverse 2025-08-09 18:37:50 +03:00
parent c84e0ffbdb
commit fa748fbd18
7 changed files with 133 additions and 76 deletions

View file

@ -1,30 +0,0 @@
use crate::{Meta, Scrape, Scraper, Torrent};
use rocket::{State, serde::Serialize};
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Format {
pub created: Option<String>,
pub files: String,
pub indexed: String,
pub magnet: String,
pub scrape: Option<Scrape>,
pub size: String,
pub torrent: Torrent,
}
impl Format {
pub fn from_torrent(torrent: Torrent, scraper: &State<Scraper>, meta: &State<Meta>) -> 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,
}
}
}

View file

@ -3,7 +3,6 @@ extern crate rocket;
mod config; mod config;
mod feed; mod feed;
mod format;
mod meta; mod meta;
mod scraper; mod scraper;
mod storage; mod storage;
@ -11,13 +10,13 @@ mod torrent;
use config::Config; use config::Config;
use feed::Feed; use feed::Feed;
use format::Format;
use meta::Meta; use meta::Meta;
use plurify::Plurify; use plurify::Plurify;
use rocket::{ use rocket::{
State, State,
http::Status, http::Status,
response::{content::RawXml, status::Custom}, response::{content::RawXml, status::Custom},
serde::Serialize,
}; };
use rocket_dyn_templates::{Template, context}; use rocket_dyn_templates::{Template, context};
use scraper::{Scrape, Scraper}; use scraper::{Scrape, Scraper};
@ -32,6 +31,17 @@ fn index(
storage: &State<Storage>, storage: &State<Storage>,
meta: &State<Meta>, meta: &State<Meta>,
) -> Result<Template, Custom<String>> { ) -> Result<Template, Custom<String>> {
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct R {
created: Option<String>,
files: String,
indexed: String,
magnet: String,
scrape: Option<Scrape>,
size: String,
torrent: Torrent,
}
let (total, torrents) = storage let (total, torrents) = storage
.torrents( .torrents(
Some((Sort::Modified, Order::Desc)), Some((Sort::Modified, Order::Desc)),
@ -42,7 +52,6 @@ fn index(
error!("Torrents storage read error: `{e}`"); error!("Torrents storage read error: `{e}`");
Custom(Status::InternalServerError, E.to_string()) Custom(Status::InternalServerError, E.to_string())
})?; })?;
Ok(Template::render( Ok(Template::render(
"index", "index",
context! { context! {
@ -53,13 +62,21 @@ fn index(
rows: torrents rows: torrents
.into_iter() .into_iter()
.filter_map(|t| match Torrent::from_storage(&t.bytes, t.time) { .filter_map(|t| match Torrent::from_storage(&t.bytes, t.time) {
Ok(torrent) => Some(Format::from_torrent(torrent, scraper, meta)), Ok(torrent) => Some(R {
created: torrent.creation_date.map(|t| t.format(&meta.format_time).to_string()),
files: torrent.files(),
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(),
torrent
}),
Err(e) => { Err(e) => {
error!("Torrent storage read error: `{e}`"); error!("Torrent storage read error: `{e}`");
None None
} }
}) })
.collect::<Vec<Format>>(), .collect::<Vec<R>>(),
pagination_totals: format!( pagination_totals: format!(
"Page {} / {} ({total} {} total)", "Page {} / {} ({total} {} total)",
page.unwrap_or(1), page.unwrap_or(1),
@ -81,19 +98,39 @@ fn info(
warn!("Torrent info-hash parse error: `{e}`"); warn!("Torrent info-hash parse error: `{e}`");
Custom(Status::BadRequest, Status::BadRequest.to_string()) Custom(Status::BadRequest, Status::BadRequest.to_string())
})?) { })?) {
Some(t) => Ok(Template::render( Some(t) => {
"info", #[derive(Serialize)]
context! { #[serde(crate = "rocket::serde")]
meta: meta.inner(), struct F {
torrent: Format::from_torrent( name: String,
Torrent::from_storage(&t.bytes, t.time).map_err(|e| { size: String,
error!("Torrent parse error: `{e}`"); }
Custom(Status::InternalServerError, E.to_string()) let torrent = Torrent::from_storage(&t.bytes, t.time).map_err(|e| {
})?, scraper, meta error!("Torrent parse error: `{e}`");
), Custom(Status::InternalServerError, E.to_string())
info_hash })?;
}, Ok(Template::render(
)), "info",
context! {
meta: meta.inner(),
created: torrent.creation_date.map(|t| t.format(&meta.format_time).to_string()),
files_total: torrent.files(),
files_list: torrent.files.as_ref().map(|f| {
f.iter()
.map(|f| F {
name: f.name(),
size: f.size(),
})
.collect::<Vec<F>>()
}),
indexed: torrent.time.format(&meta.format_time).to_string(),
magnet: torrent.magnet(meta.trackers.as_ref()),
scrape: scraper.scrape(info_hash),
size: torrent.size(),
torrent
},
))
}
None => Err(Custom(Status::NotFound, E.to_string())), None => Err(Custom(Status::NotFound, E.to_string())),
} }
} }

View file

@ -88,21 +88,7 @@ impl Torrent {
} }
pub fn size(&self) -> String { pub fn size(&self) -> String {
const KB: f32 = 1024.0; size(self.size)
const MB: f32 = KB * KB;
const GB: f32 = MB * KB;
let f = self.size as f32;
if f < KB {
format!("{} B", self.size)
} else if f < MB {
format!("{:.2} KB", f / KB)
} else if f < GB {
format!("{:.2} MB", f / MB)
} else {
format!("{:.2} GB", f / GB)
}
} }
pub fn magnet(&self, trackers: Option<&Vec<url::Url>>) -> String { pub fn magnet(&self, trackers: Option<&Vec<url::Url>>) -> String {
@ -120,3 +106,21 @@ impl Torrent {
b b
} }
} }
fn size(value: u64) -> String {
const KB: f32 = 1024.0;
const MB: f32 = KB * KB;
const GB: f32 = MB * KB;
let f = value as f32;
if f < KB {
format!("{value} B")
} else if f < MB {
format!("{:.2} KB", f / KB)
} else if f < GB {
format!("{:.2} MB", f / MB)
} else {
format!("{:.2} GB", f / GB)
}
}

View file

@ -6,3 +6,12 @@ pub struct File {
pub name: Option<String>, pub name: Option<String>,
pub length: u64, pub length: u64,
} }
impl File {
pub fn name(&self) -> String {
self.name.as_deref().unwrap_or("?").into()
}
pub fn size(&self) -> String {
super::size(self.length)
}
}

View file

@ -20,9 +20,9 @@
--background: #282b3c; --background: #282b3c;
--default: #ccc; --default: #ccc;
--item: #34384f; --item: #34384f;
--separator: #4f536a;
} }
body { body {
background: var(--background); background: var(--background);
color: var(--default); color: var(--default);
@ -47,6 +47,24 @@ h1, h2 {
font-size: 14px; font-size: 14px;
} }
table {
border-collapse: collapse;
width: 100%;
}
table td,
table th {
padding: 4px 8px;
}
table > thead > tr > th {
text-align: left;
}
table > tbody > tr:hover > td {
background: var(--background)
}
body > * { body > * {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@ -108,7 +126,7 @@ main > div > p {
/* item row meta, controls */ /* item row meta, controls */
main > div > div { main > div > div {
border-top: 1px #4f536a solid; border-top: 1px solid var(--separator);
margin-top: 16px; margin-top: 16px;
overflow: hidden; overflow: hidden;
padding-top: 16px; padding-top: 16px;

View file

@ -11,7 +11,7 @@
<li><span title="Indexed">{{ row.indexed }}</span></li> <li><span title="Indexed">{{ row.indexed }}</span></li>
{% if row.created %}<li><span title="Created">({{ row.created }})</span></li>{% endif %} {% if row.created %}<li><span title="Created">({{ row.created }})</span></li>{% endif %}
{% if row.size %}<li><span title="Size">{{ row.size }}</span></li>{% endif %} {% if row.size %}<li><span title="Size">{{ row.size }}</span></li>{% endif %}
<li><span title="Files">{{ row.files }}</span></li> <li><span title="Files">{{ row.files }}</span></li>
{% if row.scrape %} {% if row.scrape %}
<li><span title="Seeders" class="seeders">{{ row.scrape.seeders }}</span></li> <li><span title="Seeders" class="seeders">{{ row.scrape.seeders }}</span></li>
<li><span title="Peers" class="peers">{{ row.scrape.peers }}</span></li> <li><span title="Peers" class="peers">{{ row.scrape.peers }}</span></li>

View file

@ -1,23 +1,42 @@
{% extends "layout/default" %} {% extends "layout/default" %}
{% block content %} {% block content %}
<div> <div>
<h1>{% if torrent.name %}{{ torrent.name }}{% else %}{{ info_hash }}{% endif %}</h1> <h1>{% if torrent.name %}{{ torrent.name }}{% else %}{{ torrent.info_hash }}{% endif %}</h1>
{% if torrent.comment %}<p>{{ torrent.comment }}</p>{% endif %} {% if torrent.comment %}<p>{{ torrent.comment }}</p>{% endif %}
<div> <div>
<ul> <ul>
<li><span title="Indexed">{{ torrent.indexed }}</span></li> <li><span title="Indexed">{{ indexed }}</span></li>
{% if torrent.created %}<li><span title="Created">({{ torrent.created }})</span></li>{% endif %} {% if created %}<li><span title="Created">({{ created }})</span></li>{% endif %}
{% if torrent.size %}<li><span title="Size">{{ torrent.size }}</span></li>{% endif %} <li><span title="Size">{{ size }}</span></li>
<li><span title="Files">{{ torrent.files }}</span></li> <li><span title="Files">{{ files_total }}</span></li>
{% if torrent.scrape %} {% if scrape %}
<li><span title="Seeders" class="seeders">{{ torrent.scrape.seeders }}</span></li> <li><span title="Seeders" class="seeders">{{ scrape.seeders }}</span></li>
<li><span title="Peers" class="peers">{{ torrent.scrape.peers }}</span></li> <li><span title="Peers" class="peers">{{ scrape.peers }}</span></li>
<li><span title="Leechers" class="leechers">{{ torrent.scrape.leechers }}</span></li> <li><span title="Leechers" class="leechers">{{ scrape.leechers }}</span></li>
{% endif %} {% endif %}
</ul> </ul>
<div> <div>
<a rel="nofollow" href="{{ torrent.magnet }}" title="Get magnet" class="action magnet"></a> <a rel="nofollow" href="{{ magnet }}" title="Get magnet" class="action magnet"></a>
</div> </div>
</div> </div>
<div></div>
{% if files_list %}
<table>
<thead>
<tr>
<th>File</th>
<th>Size</th>
</tr>
</thead>
<tbody>
{% for file in files_list %}
<tr>
<td>{{ file.name }}</td>
<td>{{ file.size }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div> </div>
{% endblock content %} {% endblock content %}