From e7c310a1df2b72423f11ff6791c41b129054dcc5 Mon Sep 17 00:00:00 2001 From: Igor Katson Date: Thu, 8 Jul 2021 23:49:25 +0100 Subject: [PATCH] JSON torrent detail api --- crates/librqbit/src/http_api.rs | 81 ++++++++++---------- crates/librqbit_core/src/torrent_metainfo.rs | 20 +++++ 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index 627cb8f..9833508 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -3,53 +3,12 @@ use std::sync::Arc; use parking_lot::RwLock; use serde::Serialize; -use std::io::Write; use std::time::{Duration, Instant}; use warp::Filter; use crate::torrent_manager::TorrentManagerHandle; use crate::torrent_state::StatsSnapshot; -// enum Response { -// NotFound(usize), -// OkJson(T), -// Ok(B), -// } - -// impl warp::Reply for Response -// where T: Serialize + Send, -// B: Into + Send -// { -// fn into_response(self) -> warp::reply::Response - -// { -// match self { -// Response::NotFound(idx) => { -// let mut response = warp::reply::Response::new(warp::hyper::Body::from(format!( -// "torrent {} not found", -// idx -// ))); -// *response.status_mut() = warp::http::StatusCode::NOT_FOUND; -// response -// } -// Response::OkJson(body) => { -// match serde_json::to_vec_pretty(&body) { -// Ok(body) => { -// let mut response = warp::reply::Response::new(warp::hyper::Body::from(body)); -// response.headers_mut().insert("content-type", warp::http::HeaderValue::from_static("application/json")); -// response -// } -// Err(e) => { -// todo!() -// } -// } - -// }, -// Response::Ok(body) => warp::reply::Response::new(body.into()), -// } -// } -// } - struct Inner { startup_time: Instant, torrent_managers: RwLock>, @@ -96,6 +55,18 @@ struct TorrentListResponse { torrents: Vec, } +#[derive(Serialize)] +struct TorrentDetailsResponseFile { + name: Option, + length: u64, +} + +#[derive(Serialize)] +struct TorrentDetailsResponse { + info_hash: String, + files: Vec, +} + #[derive(Serialize)] struct StatsResponse { snapshot: StatsSnapshot, @@ -125,6 +96,21 @@ impl Inner { } } + fn api_torrent_details(&self, idx: usize) -> Option { + let handle = self.mgr_handle(idx)?; + let info_hash = hex::encode(handle.torrent_state().info_hash()); + let files = handle + .torrent_state() + .info() + .iter_filenames_and_lengths() + .map(|(filename_it, length)| { + let name = filename_it.to_string().ok(); + TorrentDetailsResponseFile { name, length } + }) + .collect(); + Some(TorrentDetailsResponse { info_hash, files }) + } + fn api_stats(&self, idx: usize) -> Option { let mgr = self.mgr_handle(idx)?; let snapshot = mgr.torrent_state().stats_snapshot(); @@ -204,6 +190,11 @@ impl HttpApi { move || json_response(inner.api_torrent_list()) }); + let torrent_details = warp::path!(usize).map({ + let inner = inner.clone(); + move |idx| json_or_404(idx, inner.api_torrent_details(idx)) + }); + let dump_haves = warp::path!(usize / "haves").map({ let inner = inner.clone(); move |idx| json_or_404(idx, inner.api_dump_haves(idx)) @@ -214,9 +205,15 @@ impl HttpApi { move |idx| json_or_404(idx, inner.api_stats(idx)) }); - let router = list.or(dump_haves).or(dump_stats); + let router = list.or(torrent_details).or(dump_haves).or(dump_stats); warp::serve(router).run(addr).await; Ok(()) } } + +impl Default for HttpApi { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/librqbit_core/src/torrent_metainfo.rs b/crates/librqbit_core/src/torrent_metainfo.rs index b11be90..33b270e 100644 --- a/crates/librqbit_core/src/torrent_metainfo.rs +++ b/crates/librqbit_core/src/torrent_metainfo.rs @@ -85,6 +85,26 @@ where } impl<'a, ByteBuf> FileIteratorName<'a, ByteBuf> { + pub fn to_string(&self) -> anyhow::Result + where + ByteBuf: AsRef<[u8]>, + { + let mut it = self.iter_components(); + let mut buf = it + .next() + .and_then(|v| v) + .map(|v| std::str::from_utf8(v.as_ref())) + .ok_or_else(|| anyhow::anyhow!("empty filename"))?? + .to_string(); + for bit in it { + buf.push('/'); + let bit = bit + .map(|v| std::str::from_utf8(v.as_ref())) + .ok_or_else(|| anyhow::anyhow!("empty filename"))??; + buf.push_str(bit); + } + Ok(buf) + } pub fn to_pathbuf(&self) -> anyhow::Result where ByteBuf: AsRef<[u8]>,