terminal-info 1.5.0

An extensible terminal information CLI and developer toolbox
Documentation
use std::io;
use std::time::Instant;

use reqwest::blocking::Client;
use serde::Serialize;

use crate::builtins::format_bytes;
use crate::output::{error_prefix, success_prefix};

const SPEEDTEST_URL: &str = "https://speed.cloudflare.com/__down?bytes=5000000";
const SPEEDTEST_LABEL: &str = "Cloudflare";

#[derive(Serialize)]
struct NetworkSpeedView {
    target: String,
    url: String,
    bytes_downloaded: u64,
    duration_ms: f64,
    estimated_mbps: f64,
    time_to_first_byte_ms: f64,
}

pub fn show_network_speed() -> Result<(), String> {
    let client = Client::builder()
        .connect_timeout(std::time::Duration::from_secs(3))
        .timeout(std::time::Duration::from_secs(20))
        .build()
        .map_err(|err| format!("Failed to create HTTP client: {err}"))?;

    let request_started = Instant::now();
    let mut response = client
        .get(SPEEDTEST_URL)
        .send()
        .map_err(|err| format!("Failed to start network speed test: {err}"))?
        .error_for_status()
        .map_err(|err| format!("Network speed test request failed: {err}"))?;
    let first_byte_ms = request_started.elapsed().as_secs_f64() * 1000.0;

    let download_started = Instant::now();
    let bytes_downloaded = io::copy(&mut response, &mut io::sink())
        .map_err(|err| format!("Failed to read speed test response: {err}"))?;
    let duration_ms = download_started.elapsed().as_secs_f64() * 1000.0;
    let estimated_mbps = if duration_ms > 0.0 {
        (bytes_downloaded as f64 * 8.0) / (duration_ms / 1000.0) / 1_000_000.0
    } else {
        0.0
    };

    let view = NetworkSpeedView {
        target: SPEEDTEST_LABEL.to_string(),
        url: SPEEDTEST_URL.to_string(),
        bytes_downloaded,
        duration_ms,
        estimated_mbps,
        time_to_first_byte_ms: first_byte_ms,
    };

    if crate::output::json_output() {
        println!(
            "{}",
            serde_json::to_string_pretty(&view).unwrap_or_else(|_| "{}".to_string())
        );
        return Ok(());
    }

    println!("{} Target: {}", success_prefix(), view.target);
    println!(
        "{} Downloaded: {}",
        success_prefix(),
        format_bytes(view.bytes_downloaded)
    );
    println!(
        "{} Time to first byte: {:.1} ms",
        success_prefix(),
        view.time_to_first_byte_ms
    );
    println!(
        "{} Transfer time: {:.1} ms",
        success_prefix(),
        view.duration_ms
    );
    if view.estimated_mbps > 0.0 {
        println!(
            "{} Estimated download speed: {:.2} Mbps",
            success_prefix(),
            view.estimated_mbps
        );
    } else {
        println!("{} Estimated download speed: unavailable", error_prefix());
    }

    Ok(())
}