tabiew 0.14.0

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use std::io::Read;

use tempfile::NamedTempFile;
use url::Url;

use crate::{AppResult, misc::http};

const CHUNK_SIZE: usize = 16_384;

pub struct Download {
    reader: Box<dyn Read + Send>,
    buffer: Box<[u8]>,
    downloaded: u64,
    total: Option<u64>,
    done: bool,
}

impl Download {
    pub fn new(url: &Url) -> AppResult<Self> {
        let total = download_size(url).ok().filter(|&n| n > 0);
        let reader = http::get(url).call()?.into_body().into_reader();
        Ok(Self {
            reader: Box::new(reader),
            buffer: vec![0u8; CHUNK_SIZE].into_boxed_slice(),
            downloaded: 0,
            total,
            done: false,
        })
    }

    pub fn next_chunk(&mut self) -> Option<&[u8]> {
        if self.done {
            return None;
        }
        match self.reader.read(&mut self.buffer) {
            Ok(0) => {
                self.done = true;
                None
            }
            Ok(n) => {
                self.downloaded += n as u64;
                Some(&self.buffer[..n])
            }
            Err(_) => {
                self.done = true;
                None
            }
        }
    }

    pub fn downloaded(&self) -> u64 {
        self.downloaded
    }

    pub fn total(&self) -> Option<u64> {
        self.total
    }

    pub fn percent(&self) -> Option<u16> {
        self.total
            .and_then(|tt| (self.downloaded * 100).checked_div(tt).map(|v| v as u16))
    }
}

impl std::fmt::Debug for Download {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Downloader")
            .field("downloaded", &self.downloaded)
            .field("total", &self.total)
            .field("done", &self.done)
            .finish()
    }
}

pub fn download_to_temp(url: &Url) -> AppResult<NamedTempFile> {
    let mut temp = NamedTempFile::new()?;
    let response = ureq::get(url.as_str()).call()?;
    std::io::copy(&mut response.into_body().into_reader(), temp.as_file_mut())?;
    Ok(temp)
}

pub fn download_size(url: &Url) -> AppResult<u64> {
    let respnse = http::head(url)
        .header("Accept-Encoding", "identity")
        .call()?;
    if let Some(cl) = respnse.headers().get("Content-Length") {
        let s = cl.to_str()?;
        let v = s.parse::<u64>()?;
        Ok(v)
    } else {
        Ok(0)
    }
}