tabiew 0.13.1

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use std::{
    fmt::Debug,
    io::Write,
    sync::{
        Arc,
        atomic::{AtomicU64, Ordering},
    },
    thread::JoinHandle,
};

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

use crate::{
    AppResult,
    io::reader::{DataFrameReader, NamedFrames, ReaderSource},
};

pub trait Reader: DataFrameReader + Debug + Send + Sync + 'static {}

impl<T> Reader for T where T: DataFrameReader + Debug + Send + Sync + 'static {}

#[derive(Debug)]
pub struct BackgroundDownloaderAndRead {
    info: DownloadInfo,
    hndl: JoinHandle<AppResult<NamedFrames>>,
}

impl BackgroundDownloaderAndRead {
    pub fn new(url: Url, df_reader: Arc<dyn Reader>) -> Self {
        let info = DownloadInfo::default();
        BackgroundDownloaderAndRead {
            info: info.clone(),
            hndl: std::thread::spawn(move || {
                info.set_total(download_size(&url)?);
                if info.total() == 0 {
                    todo!() // Just testing
                }
                let mut reader = ureq::get(url.as_str()).call()?.into_reader();
                let mut temp = NamedTempFile::new()?;
                let writer = temp.as_file_mut();
                let mut buffer = [0_u8; 16_384];
                loop {
                    let n = reader.read(&mut buffer)?;
                    if n == 0 {
                        break;
                    }
                    info.add_progress(n as u64);
                    writer.write_all(&buffer[..n])?;
                }
                let nf =
                    df_reader.read_to_data_frames(ReaderSource::File(temp.path().to_owned()))?;
                Ok(nf)
            }),
        }
    }

    pub fn running(&self) -> bool {
        !self.hndl.is_finished()
    }

    pub fn info(&self) -> &DownloadInfo {
        &self.info
    }

    pub fn join(self) -> AppResult<NamedFrames> {
        // TODO: fix unwrap
        self.hndl.join().unwrap()
    }
}

#[derive(Debug, Default, Clone)]
pub struct DownloadInfo {
    pg: Arc<AtomicU64>,
    tt: Arc<AtomicU64>,
}

impl DownloadInfo {
    pub fn progress(&self) -> u64 {
        self.pg.load(Ordering::Relaxed)
    }

    fn add_progress(&self, pg: u64) {
        self.pg.fetch_add(pg, Ordering::Relaxed);
    }

    pub fn total(&self) -> u64 {
        self.tt.load(Ordering::Relaxed)
    }

    fn set_total(&self, tt: u64) {
        self.tt.store(tt, Ordering::Relaxed);
    }

    pub fn percent(&self) -> Option<u16> {
        let pg = self.progress() * 100;
        let tt = self.total();
        pg.checked_div(tt).map(|u| u as u16)
    }
}

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_reader(), temp.as_file_mut())?;
    Ok(temp)
}

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