init torrent info page

This commit is contained in:
yggverse 2025-09-08 16:14:55 +03:00
parent 90d8e48b55
commit 43052a0d38
2 changed files with 150 additions and 66 deletions

View file

@ -1,4 +1,14 @@
pub fn size(value: u64) -> String { use librqbit_core::torrent_metainfo::TorrentMetaV1Owned;
use plurify::Plurify;
use url::Url;
pub fn files(meta: &TorrentMetaV1Owned) -> String {
let total = meta.info.files.as_ref().map(|f| f.len()).unwrap_or(1);
format!("{total} {}", total.plurify(&["file", "files", "files"]))
}
pub fn size(meta: &TorrentMetaV1Owned) -> String {
fn s(value: u64) -> String {
const KB: f32 = 1024.0; const KB: f32 = 1024.0;
const MB: f32 = KB * KB; const MB: f32 = KB * KB;
const GB: f32 = MB * KB; const GB: f32 = MB * KB;
@ -15,3 +25,26 @@ pub fn size(value: u64) -> String {
format!("{:.2} GB", f / GB) format!("{:.2} GB", f / GB)
} }
} }
s(meta
.info
.files
.as_ref()
.map(|files| files.iter().map(|file| file.length).sum::<u64>())
.unwrap_or_default()
+ meta.info.length.unwrap_or_default())
}
pub fn magnet(meta: &TorrentMetaV1Owned, trackers: Option<&Vec<Url>>) -> String {
let mut b = format!("magnet:?xt=urn:btih:{}", meta.info_hash.as_string());
if let Some(ref n) = meta.info.name {
b.push_str("&dn=");
b.push_str(&urlencoding::encode(&n.to_string()))
}
if let Some(t) = trackers {
for tracker in t {
b.push_str("&tr=");
b.push_str(&urlencoding::encode(tracker.as_str()))
}
}
b
}

View file

@ -2,23 +2,25 @@ mod config;
mod format; mod format;
use anyhow::Result; use anyhow::Result;
use btracker_fs::public::{Order, Public, Sort}; use btracker_fs::public::{Order, Public, Sort, Torrent};
use chrono::Local; use chrono::Local;
use clap::Parser; use clap::Parser;
use config::Config; use config::Config;
use librqbit_core::torrent_metainfo::{TorrentMetaV1Owned, torrent_from_bytes}; use librqbit_core::{
Id20,
torrent_metainfo::{TorrentMetaV1Owned, torrent_from_bytes},
};
use log::*; use log::*;
use native_tls::{HandshakeError, Identity, TlsAcceptor, TlsStream}; use native_tls::{HandshakeError, Identity, TlsAcceptor, TlsStream};
use plurify::Plurify;
use std::{ use std::{
fs::File, fs::File,
io::{Read, Write}, io::{Read, Write},
net::{SocketAddr, TcpListener, TcpStream}, net::{SocketAddr, TcpListener, TcpStream},
str::FromStr,
sync::Arc, sync::Arc,
thread, thread,
}; };
use titanite::*; use titanite::*;
use url::Url;
fn main() -> Result<()> { fn main() -> Result<()> {
if std::env::var("RUST_LOG").is_ok() { if std::env::var("RUST_LOG").is_ok() {
@ -153,10 +155,10 @@ fn response(
stream: &mut TlsStream<TcpStream>, stream: &mut TlsStream<TcpStream>,
) { ) {
debug!("Incoming request from `{peer}` to `{}`", request.url.path()); debug!("Incoming request from `{peer}` to `{}`", request.url.path());
let p = request.url.path().trim_matches('/');
// try index page // try index page
if request.url.path().trim_end_matches("/").is_empty() { if p.is_empty() {
return send( send(
&match index( &match index(
config, config,
public, public,
@ -184,16 +186,67 @@ fn response(
} }
}, },
stream, stream,
|result| match result { |result| {
Ok(()) => debug!("Home page request from peer `{peer}`"), if let Err(e) = result {
Err(e) => error!("Internal server error on handle peer `{peer}` request: `{e}`"), error!("Internal server error on handle peer `{peer}` request: `{e}`")
}
}, },
); )
} }
// try info page // try info page
todo!() else if let Ok(id) = Id20::from_str(p)
&& let Some(torrent) = public.torrent(id)
{
send(
&match info(config, torrent) {
Ok(data) => response::success::Default {
data: data.as_bytes(),
meta: response::success::default::Meta {
mime: "text/gemini".to_string(),
},
}
.into_bytes(),
Err(e) => {
error!("Internal server error on handle peer `{peer}` request: `{e}`");
response::failure::temporary::General {
message: Some("Internal server error".to_string()),
}
.into_bytes()
}
},
stream,
|result| {
if let Err(e) = result {
error!("Internal server error on handle peer `{peer}` request: `{e}`")
}
},
)
}
// not found
else {
warn!(
"Requested resource `{}` not found by peer `{peer}`",
request.url.as_str()
);
send(
&response::Failure::Permanent(response::failure::Permanent::NotFound(
response::failure::permanent::NotFound { message: None },
))
.into_bytes(),
stream,
|result| {
if let Err(e) = result {
error!(
"Internal server error on handle peer `{peer}` request `{}`: `{e}`",
request.url.as_str()
)
}
},
)
}
} }
fn send(data: &[u8], stream: &mut TlsStream<TcpStream>, callback: impl FnOnce(Result<()>)) {
fn close(stream: &mut TlsStream<TcpStream>) -> Result<()> { fn close(stream: &mut TlsStream<TcpStream>) -> Result<()> {
stream.flush()?; stream.flush()?;
// close connection gracefully // close connection gracefully
@ -201,8 +254,6 @@ fn close(stream: &mut TlsStream<TcpStream>) -> Result<()> {
stream.shutdown()?; stream.shutdown()?;
Ok(()) Ok(())
} }
fn send(data: &[u8], stream: &mut TlsStream<TcpStream>, callback: impl FnOnce(Result<()>)) {
callback((|| { callback((|| {
stream.write_all(data)?; stream.write_all(data)?;
close(stream)?; close(stream)?;
@ -210,7 +261,11 @@ fn send(data: &[u8], stream: &mut TlsStream<TcpStream>, callback: impl FnOnce(Re
})()); })());
} }
// rotes
fn index(config: &Config, public: &Public, page: Option<usize>) -> Result<String> { fn index(config: &Config, public: &Public, page: Option<usize>) -> Result<String> {
use plurify::Plurify;
let (total, torrents) = public.torrents( let (total, torrents) = public.torrents(
None, // @TODO None, // @TODO
Some((Sort::Modified, Order::Desc)), Some((Sort::Modified, Order::Desc)),
@ -246,13 +301,9 @@ fn index(config: &Config, public: &Public, page: Option<usize>) -> Result<String
b.push(format!( b.push(format!(
"{} • {} • {}\n", "{} • {} • {}\n",
torrent.time.format(&config.date), torrent.time.format(&config.date),
size(&i), format::size(&i),
files(&i), format::files(&i),
)) ))
/*b.push(format!(
"=> {} Magnet\n",
magnet(&i, config.tracker.as_ref())
))*/
} }
b.push("## Navigation\n".into()); b.push("## Navigation\n".into());
@ -283,33 +334,33 @@ fn index(config: &Config, public: &Public, page: Option<usize>) -> Result<String
Ok(b.join("\n")) Ok(b.join("\n"))
} }
fn files(meta: &TorrentMetaV1Owned) -> String { fn info(config: &Config, torrent: Torrent) -> Result<String> {
let total = meta.info.files.as_ref().map(|f| f.len()).unwrap_or(1); let i: TorrentMetaV1Owned = torrent_from_bytes(&torrent.bytes)?;
format!("{total} {}", total.plurify(&["file", "files", "files"]))
}
fn size(meta: &TorrentMetaV1Owned) -> String { let mut b = Vec::new();
format::size(
meta.info b.push(format!("# {}\n", config.name));
.files
b.push(format!(
"## {}\n",
i.info
.name
.as_ref() .as_ref()
.map(|files| files.iter().map(|file| file.length).sum::<u64>()) .map(|n| n.to_string())
.unwrap_or_default() .unwrap_or_default()
+ meta.info.length.unwrap_or_default(), ));
)
}
fn magnet(meta: &TorrentMetaV1Owned, trackers: Option<&Vec<Url>>) -> String { b.push(format!(
let mut b = format!("magnet:?xt=urn:btih:{}", meta.info_hash.as_string()); "{} • {} • {}\n",
if let Some(ref n) = meta.info.name { torrent.time.format(&config.date),
b.push_str("&dn="); format::size(&i),
b.push_str(&urlencoding::encode(&n.to_string())) format::files(&i),
} ));
if let Some(t) = trackers {
for tracker in t { b.push(format!(
b.push_str("&tr="); "=> {} Magnet\n",
b.push_str(&urlencoding::encode(tracker.as_str())) format::magnet(&i, config.tracker.as_ref())
} ));
}
b Ok(b.join("\n"))
} }