dw-rs 0.1.0

A blazingly fast, parallel download accelerator written in Rust.
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};

pub struct BandwidthMonitor {
    bytes_transferred: AtomicU64,
    samples: Mutex<Vec<(std::time::Instant, u64)>>,
}

impl BandwidthMonitor {
    pub fn new() -> Self {
        Self {
            bytes_transferred: AtomicU64::new(0),
            samples: Mutex::new(Vec::new()),
        }
    }

    pub fn record_bytes(&self, bytes: u64) {
        self.bytes_transferred.fetch_add(bytes, Ordering::Relaxed);
        let now = std::time::Instant::now();

        let mut samples = self.samples.lock().unwrap();
        samples.push((now, bytes));

        samples.retain(|(time, _)| now.duration_since(*time).as_secs() < 10);
    }

    pub fn get_current_speed(&self) -> f64 {
        let samples = self.samples.lock().unwrap();
        if samples.is_empty() {
            return 0.0;
        }

        let total_bytes: u64 = samples.iter().map(|(_, bytes)| bytes).sum();
        let duration = samples
            .last()
            .map(|(time, _)| time.duration_since(samples[0].0).as_secs_f64())
            .unwrap_or(0.001);

        if duration == 0.0 {
            return 0.0;
        }

        total_bytes as f64 / duration / 1024.0 / 1024.0
    }
}

pub fn extract_filename(url: &str) -> Option<String> {
    url::Url::parse(url).ok().and_then(|u| {
        u.path_segments()
            .and_then(|mut segments| segments.next_back())
            .and_then(|s| {
                if s.is_empty() {
                    None
                } else {
                    Some(s.to_string())
                }
            })
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_extract_filename_from_simple_url() {
        let url = "http://example.com/some/path/to/file.zip";
        assert_eq!(extract_filename(url), Some("file.zip".to_string()));
    }

    #[test]
    fn test_extract_filename_with_query_params() {
        let url = "http://example.com/archive.tar.gz?token=123&user=abc";
        assert_eq!(extract_filename(url), Some("archive.tar.gz".to_string()));
    }

    #[test]
    fn test_extract_filename_from_root() {
        let url = "http://example.com/";
        assert_eq!(extract_filename(url), None);
    }

    #[test]
    fn test_extract_filename_without_extension() {
        let url = "http://example.com/some/directory/resource";
        assert_eq!(extract_filename(url), Some("resource".to_string()));
    }

    #[test]
    fn test_extract_filename_from_invalid_url() {
        let url = "not-a-valid-url";
        assert_eq!(extract_filename(url), None);
    }

    #[test]
    fn test_bandwidth_monitor_initial_state() {
        let monitor = BandwidthMonitor::new();
        assert_eq!(monitor.bytes_transferred.load(Ordering::Relaxed), 0);
        assert_eq!(monitor.get_current_speed(), 0.0);
    }

    #[test]
    fn test_bandwidth_monitor_records_bytes() {
        let monitor = BandwidthMonitor::new();
        monitor.record_bytes(1024);
        monitor.record_bytes(2048);
        assert_eq!(monitor.bytes_transferred.load(Ordering::Relaxed), 3072);
    }

    #[test]
    fn test_bandwidth_monitor_calculates_speed() {
        let monitor = BandwidthMonitor::new();

        monitor.record_bytes(1024 * 1024);
        thread::sleep(Duration::from_millis(500));
        monitor.record_bytes(1024 * 1024);

        let speed = monitor.get_current_speed();
        assert!(
            speed > 1.0 && speed < 5.0,
            "Speed should be in a reasonable range (calculated: {} MB/s)",
            speed
        );
    }

    #[test]
    fn test_bandwidth_monitor_sample_retention() {
        let monitor = BandwidthMonitor::new();
        monitor.record_bytes(100);

        thread::sleep(Duration::from_secs(11));

        monitor.record_bytes(100);

        let speed = monitor.get_current_speed();
        assert!(speed >= 0.0, "Speed should not be negative.");
    }
}