Initialization progress reporting

This commit is contained in:
Igor Katson 2023-11-24 15:04:36 +00:00
parent b79a21179f
commit 876afbf41b
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
9 changed files with 109 additions and 40 deletions

View file

@ -11,6 +11,7 @@ use librqbit_core::id20::Id20;
use librqbit_core::torrent_metainfo::TorrentMetaV1Info;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::{info, warn};
@ -23,7 +24,7 @@ use crate::session::{
};
use crate::torrent_state::peer::stats::snapshot::{PeerStatsFilter, PeerStatsSnapshot};
use crate::torrent_state::stats::snapshot::StatsSnapshot;
use crate::torrent_state::ManagedTorrentHandle;
use crate::torrent_state::{ManagedTorrentHandle, ManagedTorrentState, TorrentStateLive};
// Public API
#[derive(Clone)]
@ -100,11 +101,18 @@ impl HttpApi {
state.api_dump_haves(idx)
}
async fn torrent_stats(
async fn torrent_stats_v0(
State(state): State<ApiState>,
Path(idx): Path<usize>,
) -> Result<impl IntoResponse> {
state.api_stats(idx).map(axum::Json)
state.api_stats_v0(idx).map(axum::Json)
}
async fn torrent_stats_v1(
State(state): State<ApiState>,
Path(idx): Path<usize>,
) -> Result<impl IntoResponse> {
state.api_stats_v1(idx).map(axum::Json)
}
async fn peer_stats(
@ -137,7 +145,8 @@ impl HttpApi {
.route("/torrents", get(torrents_list).post(torrents_post))
.route("/torrents/:id", get(torrent_details))
.route("/torrents/:id/haves", get(torrent_haves))
.route("/torrents/:id/stats", get(torrent_stats))
.route("/torrents/:id/stats", get(torrent_stats_v0))
.route("/torrents/:id/stats/v1", get(torrent_stats_v1))
.route("/torrents/:id/peer_stats", get(peer_stats))
.route("/torrents/:id/pause", post(torrent_action_pause))
.route("/torrents/:id/start", post(torrent_action_start));
@ -196,7 +205,7 @@ impl HttpApi {
type Result<T> = std::result::Result<T, ApiError>;
#[derive(Serialize)]
#[derive(Serialize, Default)]
struct Speed {
mbps: f64,
human_readable: String,
@ -261,8 +270,8 @@ impl Serialize for DurationWithHumanReadable {
}
}
#[derive(Serialize)]
struct StatsResponse {
#[derive(Serialize, Default)]
struct LiveStats {
snapshot: StatsSnapshot,
average_piece_download_time: Option<Duration>,
download_speed: Speed,
@ -270,6 +279,15 @@ struct StatsResponse {
time_remaining: Option<DurationWithHumanReadable>,
}
#[derive(Serialize)]
struct StatsResponse {
state: &'static str,
error: Option<String>,
progress_bytes: u64,
total_bytes: u64,
live: Option<LiveStats>,
}
#[derive(Serialize, Deserialize)]
pub struct ApiAddTorrentResponse {
pub id: Option<usize>,
@ -465,9 +483,7 @@ impl ApiInternal {
Ok(dht.with_routing_table(|r| r.clone()))
}
fn api_stats(&self, idx: TorrentId) -> Result<StatsResponse> {
let mgr = self.mgr_handle(idx)?;
let live = mgr.live().context("not live")?;
fn make_live_stats(&self, live: &TorrentStateLive) -> LiveStats {
let snapshot = live.stats_snapshot();
let estimator = live.speed_estimator();
@ -476,12 +492,57 @@ impl ApiInternal {
let downloaded_bytes = snapshot.downloaded_and_checked_bytes;
let downloaded_mb = downloaded_bytes as f64 / 1024f64 / 1024f64;
Ok(StatsResponse {
LiveStats {
average_piece_download_time: snapshot.average_piece_download_time(),
snapshot,
all_time_download_speed: (downloaded_mb / elapsed.as_secs_f64()).into(),
download_speed: estimator.download_mbps().into(),
time_remaining: estimator.time_remaining().map(DurationWithHumanReadable),
}
}
fn api_stats_v0(&self, idx: TorrentId) -> Result<LiveStats> {
let mgr = self.mgr_handle(idx)?;
let live = mgr.live().context("torrent not live")?;
Ok(self.make_live_stats(&live))
}
fn api_stats_v1(&self, idx: TorrentId) -> Result<StatsResponse> {
let mgr = self.mgr_handle(idx)?;
let mut resp = StatsResponse {
total_bytes: mgr.info().lengths.total_length(),
state: "",
error: None,
progress_bytes: 0,
live: None,
};
mgr.with_state(|s| {
match s {
ManagedTorrentState::Initializing(i) => {
resp.state = "initializing";
resp.progress_bytes = i.checked_bytes.load(Ordering::Relaxed);
}
ManagedTorrentState::Paused(p) => {
resp.state = "paused";
resp.progress_bytes = p.have_bytes;
}
ManagedTorrentState::Live(l) => {
resp.state = "live";
let live_stats = self.make_live_stats(l);
resp.progress_bytes = live_stats.snapshot.downloaded_and_checked_bytes;
resp.live = Some(live_stats);
}
ManagedTorrentState::Error(e) => {
resp.state = "error";
resp.error = Some(format!("{:?}", e))
}
ManagedTorrentState::None => {
resp.state = "error";
resp.error = Some("bug: torrent in broken \"None\" state".to_string());
}
}
Ok(resp)
})
}