fast-down-cli 2.7.8

超级快的下载器命令行界面
use crate::fmt;
use crossterm::{QueueableCommand, cursor, style::Print, terminal};
use fast_down::{Merge, ProgressEntry, Total};
use parking_lot::Mutex;
use std::{
    io::{self, Stderr, Write},
    sync::Arc,
    time::{Duration, Instant},
};
use tokio::task::JoinHandle;

const BLOCK_CHARS: [char; 9] = [' ', '', '', '', '', '', '', '', ''];

#[derive(Debug)]
pub struct Painter {
    pub progress: Vec<ProgressEntry>,
    pub width: u16,
    pub start: Instant,
    pub alpha: f64,
    pub file_size: u64,
    pub prev_size: u64,
    pub curr_size: u64,
    pub avg_speed: f64,
    pub repaint_duration: Duration,
    pub last_repaint_time: Instant,
    has_progress: bool,
    stderr: Stderr,
}

impl Painter {
    pub fn new(
        init_progress: Vec<ProgressEntry>,
        file_size: u64,
        progress_width: u16,
        alpha: f64,
        repaint_duration: Duration,
        start: Instant,
    ) -> io::Result<Self> {
        let init_size = init_progress.total();
        let mut stderr = io::stderr();
        stderr.queue(cursor::Hide)?;
        Ok(Self {
            progress: init_progress,
            file_size,
            width: progress_width,
            alpha,
            repaint_duration,
            start,
            prev_size: init_size,
            curr_size: init_size,
            avg_speed: 0.0,
            last_repaint_time: Instant::now(),
            has_progress: false,
            stderr,
        })
    }

    pub fn reset_progress(&mut self) {
        self.progress.clear();
        self.prev_size = 0;
        self.curr_size = 0;
        self.avg_speed = 0.0;
        self.start = Instant::now();
    }

    pub fn start_update_thread(painter_arc: Arc<Mutex<Self>>) -> JoinHandle<()> {
        let duration = {
            let painter = painter_arc.lock();
            assert_ne!(painter.width, 0);
            painter.repaint_duration
        };
        tokio::spawn(async move {
            loop {
                let should_stop = {
                    let mut painter = painter_arc.lock();
                    painter.update().unwrap();
                    painter.file_size > 0 && painter.curr_size >= painter.file_size
                };
                if should_stop {
                    break;
                }
                tokio::time::sleep(duration).await;
            }
        })
    }

    pub fn add(&mut self, p: ProgressEntry) {
        if self.width == 0 {
            return;
        }
        self.progress.merge_progress(p);
        self.curr_size = self.progress.total();
    }

    fn reset_pos(&mut self) -> io::Result<()> {
        if self.has_progress {
            self.stderr
                .queue(cursor::MoveUp(1))?
                .queue(cursor::MoveUp(1))?
                .queue(cursor::MoveToColumn(0))?;
        }
        Ok(())
    }

    pub fn update(&mut self) -> io::Result<()> {
        if self.width == 0 {
            return Ok(());
        }
        let repaint_elapsed = self.last_repaint_time.elapsed().as_millis();
        self.last_repaint_time = Instant::now();
        let curr_dsize = self.curr_size - self.prev_size;
        self.prev_size = self.curr_size;
        let curr_speed = if repaint_elapsed > 0 {
            (curr_dsize * 1000) as f64 / repaint_elapsed as f64
        } else {
            0.0
        };
        self.avg_speed = self.avg_speed * self.alpha + curr_speed * (1.0 - self.alpha);
        let line1 = if self.file_size == 0 {
            format!(
                "|{}| {:>6.2}% ({:>8}/Unknown)",
                BLOCK_CHARS[0].to_string().repeat(self.width as usize),
                0.0,
                fmt::format_size(self.curr_size as f64),
            )
        } else {
            let get_percent = (self.curr_size as f64 / self.file_size as f64) * 100.0;
            let per_bytes = self.file_size as f64 / self.width as f64;
            let mut bar_values = vec![0u64; self.width as usize];
            let mut index = 0;
            for i in 0..self.width {
                let start_byte = i as f64 * per_bytes;
                let end_byte = (start_byte + per_bytes) as u64;
                let start_byte = start_byte as u64;
                let mut block_total = 0;
                for segment in &self.progress[index..] {
                    if segment.end <= start_byte {
                        index += 1;
                        continue;
                    }
                    if segment.start >= end_byte {
                        break;
                    }
                    let overlap_start = segment.start.max(start_byte);
                    let overlap_end = segment.end.min(end_byte);
                    if overlap_start < overlap_end {
                        block_total += overlap_end - overlap_start;
                    }
                }
                bar_values[i as usize] = block_total;
            }
            let bar_str: String = bar_values
                .iter()
                .map(|&count| {
                    BLOCK_CHARS
                        [((count as f64 / per_bytes * (BLOCK_CHARS.len() - 1) as f64).round()
                            as usize)
                            .min(BLOCK_CHARS.len() - 1)]
                })
                .collect();
            format!(
                "|{}| {:>6.2}% ({:>8}/{})",
                bar_str,
                get_percent,
                fmt::format_size(self.curr_size as f64),
                fmt::format_size(self.file_size as f64),
            )
        };
        let line2 = t!(
            "progress.desc",
            time_spent = fmt::format_time(self.start.elapsed().as_secs()),
            time_left = if self.file_size == 0 { "Unknown".to_string() } else { fmt::format_time(((self.file_size - self.curr_size) as f64 / self.avg_speed) as u64) },
            speed = fmt::format_size(self.avg_speed) : {:>8},
        );
        self.reset_pos()?;
        self.stderr.queue(Print(line1))?;
        self.stderr
            .queue(terminal::Clear(terminal::ClearType::UntilNewLine))?;
        self.stderr.queue(Print("\n"))?;
        self.stderr.queue(Print(&line2))?;
        self.stderr
            .queue(terminal::Clear(terminal::ClearType::UntilNewLine))?;
        self.stderr.queue(Print("\n"))?;
        self.stderr.flush()?;
        self.has_progress = true;
        Ok(())
    }

    pub fn print(&mut self, msg: &str) -> io::Result<()> {
        self.reset_pos()?;
        for line in msg.lines() {
            self.stderr.queue(Print(line))?;
            self.stderr
                .queue(terminal::Clear(terminal::ClearType::UntilNewLine))?;
            self.stderr.queue(Print("\n"))?;
        }
        self.has_progress = false;
        self.update()?;
        Ok(())
    }
}

impl Drop for Painter {
    fn drop(&mut self) {
        let _ = self.stderr.queue(cursor::Show);
        let _ = self.stderr.flush();
    }
}