* Now can update the list of files without pausing/unpausing * Shrink a few functions * Reopen write when updating files * Todos * opened_file abstraction * iter_pieces_within iterator * Simplify iter_pieces_within * Simplify iter_pieces_within * Add "iter_file_details" * temporarily broken: readonly by default * Live torrent - reopen files * Reopen files after changing the list * Now reopening files read only when they are completed * Fix a bug in opened_file.rs * update todos * update help * Reconnect all peers that are idling * Add a couple fields to OpenedFile * Add a couple fields to OpenedFile * Small cleanups - use the new iterator where possible * size_of_piece_in_file function * Updating have * Include file progress * Almost nothing * ugly progress bars * bad UI, saving * its not so bad * Works now * update progress bar a bit * Reopen read-only on pause * Zero bytes isnt too bad! Doesnt break anything * fix per file progress bars * progress bar not as ugly anymore? * ui tweaks * fix a react bug * TODO.md update * Fix js + TODOs * Compute per-file progress on init * Fix stats updating live * Nothing * Nothing * cleanup ui a bit * Nothing * Final fixes * Trying to fix rust 1.73 * Sorting filenames * remove unnecessary indentation * Remove unnecessary comment
229 lines
6.2 KiB
Rust
229 lines
6.2 KiB
Rust
use std::time::Duration;
|
|
|
|
use serde::Serialize;
|
|
|
|
use super::{live::stats::snapshot::StatsSnapshot, TorrentStateLive};
|
|
use size_format::SizeFormatterBinary as SF;
|
|
|
|
#[derive(Serialize, Default, Debug)]
|
|
pub struct LiveStats {
|
|
pub snapshot: StatsSnapshot,
|
|
pub average_piece_download_time: Option<Duration>,
|
|
pub download_speed: Speed,
|
|
pub upload_speed: Speed,
|
|
pub time_remaining: Option<DurationWithHumanReadable>,
|
|
}
|
|
|
|
impl std::fmt::Display for LiveStats {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "down speed: {}", self.download_speed)?;
|
|
if let Some(time_remaining) = &self.time_remaining {
|
|
write!(f, ", eta: {time_remaining}")?;
|
|
}
|
|
write!(f, ", up speed: {}", self.upload_speed)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl From<&TorrentStateLive> for LiveStats {
|
|
fn from(live: &TorrentStateLive) -> Self {
|
|
let snapshot = live.stats_snapshot();
|
|
let down_estimator = live.down_speed_estimator();
|
|
let up_estimator = live.up_speed_estimator();
|
|
|
|
Self {
|
|
average_piece_download_time: snapshot.average_piece_download_time(),
|
|
snapshot,
|
|
download_speed: down_estimator.mbps().into(),
|
|
upload_speed: up_estimator.mbps().into(),
|
|
time_remaining: down_estimator
|
|
.time_remaining()
|
|
.map(DurationWithHumanReadable),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Serialize, Debug)]
|
|
pub enum TorrentStatsState {
|
|
#[serde(rename = "initializing")]
|
|
Initializing,
|
|
#[serde(rename = "live")]
|
|
Live,
|
|
#[serde(rename = "paused")]
|
|
Paused,
|
|
#[serde(rename = "error")]
|
|
Error,
|
|
}
|
|
|
|
impl std::fmt::Display for TorrentStatsState {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
TorrentStatsState::Initializing => f.write_str("initializing"),
|
|
TorrentStatsState::Live => f.write_str("live"),
|
|
TorrentStatsState::Paused => f.write_str("paused"),
|
|
TorrentStatsState::Error => f.write_str("error"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct TorrentStats {
|
|
pub state: TorrentStatsState,
|
|
pub file_progress: Vec<u64>,
|
|
pub error: Option<String>,
|
|
pub progress_bytes: u64,
|
|
pub uploaded_bytes: u64,
|
|
pub total_bytes: u64,
|
|
pub finished: bool,
|
|
pub live: Option<LiveStats>,
|
|
}
|
|
|
|
impl std::fmt::Display for TorrentStats {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}: ", self.state)?;
|
|
if let Some(error) = &self.error {
|
|
return write!(f, "{error}");
|
|
}
|
|
write!(
|
|
f,
|
|
"{} ({})",
|
|
self.progress_percent_human_readable(),
|
|
self.progress_bytes_human_readable()
|
|
)?;
|
|
if let Some(live) = &self.live {
|
|
write!(f, " [{live}]")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl TorrentStats {
|
|
pub fn progress_percent_human_readable(&self) -> impl std::fmt::Display {
|
|
struct Percents {
|
|
progress: u64,
|
|
total: u64,
|
|
}
|
|
impl std::fmt::Display for Percents {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
if self.total == 0 {
|
|
return write!(f, "N/A");
|
|
}
|
|
let pct = self.progress as f64 / self.total as f64 * 100f64;
|
|
write!(f, "{pct:.2}%")
|
|
}
|
|
}
|
|
Percents {
|
|
progress: self.progress_bytes,
|
|
total: self.total_bytes,
|
|
}
|
|
}
|
|
|
|
pub fn progress_bytes_human_readable(&self) -> impl std::fmt::Display {
|
|
struct Progress {
|
|
progress: u64,
|
|
total: u64,
|
|
}
|
|
impl std::fmt::Display for Progress {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{} / {}", SF::new(self.progress), SF::new(self.total))
|
|
}
|
|
}
|
|
Progress {
|
|
progress: self.progress_bytes,
|
|
total: self.total_bytes,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_seconds_to_time(seconds: u64, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
let hours = seconds / 3600;
|
|
let minutes = (seconds % 3600) / 60;
|
|
let seconds = seconds % 60;
|
|
|
|
if hours > 0 {
|
|
write!(f, "{}h {}m", hours, minutes)
|
|
} else if minutes > 0 {
|
|
write!(f, "{}m {}s", minutes, seconds)
|
|
} else {
|
|
write!(f, "{}s", seconds)
|
|
}
|
|
}
|
|
|
|
pub struct DurationWithHumanReadable(Duration);
|
|
|
|
impl core::fmt::Display for DurationWithHumanReadable {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
format_seconds_to_time(self.0.as_secs(), f)
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for DurationWithHumanReadable {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self)
|
|
}
|
|
}
|
|
|
|
impl Serialize for DurationWithHumanReadable {
|
|
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
#[derive(Serialize)]
|
|
struct Tmp {
|
|
duration: Duration,
|
|
human_readable: String,
|
|
}
|
|
Tmp {
|
|
duration: self.0,
|
|
human_readable: format!("{}", self),
|
|
}
|
|
.serialize(serializer)
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct Speed {
|
|
pub mbps: f64,
|
|
}
|
|
|
|
impl Speed {
|
|
fn new(mbps: f64) -> Self {
|
|
Self { mbps }
|
|
}
|
|
}
|
|
|
|
impl From<f64> for Speed {
|
|
fn from(mbps: f64) -> Self {
|
|
Self::new(mbps)
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Display for Speed {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{:.2} MiB/s", self.mbps)
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for Speed {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self)
|
|
}
|
|
}
|
|
|
|
impl Serialize for Speed {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
#[derive(Serialize)]
|
|
struct Tmp {
|
|
mbps: f64,
|
|
human_readable: String,
|
|
}
|
|
Tmp {
|
|
mbps: self.mbps,
|
|
human_readable: format!("{}", self),
|
|
}
|
|
.serialize(serializer)
|
|
}
|
|
}
|