gutenberg-rs 0.1.0

This crate is used to get information and data from gutenberg (https://www.gutenberg.org/)
Documentation
use crate::error::Error;
use bzip2::read::BzDecoder;
use futures_util::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::Client;
use std::cmp::min;
use std::fs::File;
use std::io::{Read, Seek, Write};
use tar::Archive;

pub async fn download_file(url: &str, path: &str) -> Result<(), Error> {
    let client = &Client::new();
    let res = client.get(url).send().await.or(Err(Error::InvalidRequest(
        format!("Failed to GET from '{}'", &url).to_string(),
    )))?;

    let total_size = res.content_length().ok_or(Error::InvalidRequest(
        format!("Bad file from '{}'", &url).to_string(),
    ))?;

    let mut file;
    let mut downloaded: u64 = 0;
    let mut stream = res.bytes_stream();

    if std::path::Path::new(path).exists() {
        file = std::fs::OpenOptions::new()
            .read(true)
            .append(true)
            .open(path)?;

        let file_size = std::fs::metadata(path)?.len();
        file.seek(std::io::SeekFrom::Start(file_size))?;
        downloaded = file_size;
    } else {
        file = File::create(path).or(Err(Error::InvalidRequest(format!(
            "Failed to create file '{}'",
            path
        ))))?;
    }

    let pb = ProgressBar::new(total_size);
    pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.white/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")?
    .progress_chars(""));

    pb.set_message(format!("Downloading {} from {}", path, url));

    while let Some(item) = stream.next().await {
        let chunk = item.or(Err(Error::InvalidRequest(format!(
            "Error while downloading file"
        ))))?;
        file.write(&chunk).or(Err(Error::InvalidRequest(format!(
            "Error while writing to file"
        ))))?;
        let new = min(downloaded + (chunk.len() as u64), total_size);
        downloaded = new;
        pb.set_position(new);
    }

    pb.finish();
    return Ok(());
}

pub fn decompress_tar_bz(path: &str) -> Result<(), Error> {
    let (total_archive_size, bz_filename) = decompress_bz(path)?;
    decompress_tar(bz_filename.as_str(), total_archive_size)?;
    Ok(())
}

pub fn decompress_bz(path: &str) -> Result<(u64, String), Error> {
    let bz_file = File::open(path)?;
    let bz_size = bz_file.metadata()?.len();
    let new_filename = &path[..path.len() - 3];

    let pb = ProgressBar::new(bz_size);
    pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.white/blue}] {bytes}/{total_bytes} ({eta})")
    ?.progress_chars(""));

    pb.set_message(format!("Decompressing {} to {}", path, new_filename));

    let mut decoder = BzDecoder::new(bz_file);
    let big_data_size = 1024 * 1024;
    let mut total_archive_size = 0 as u64;
    let mut output_file = File::create(new_filename)?;

    loop {
        let mut read_buffer = vec![0; big_data_size];
        let data_len = decoder.read(&mut read_buffer)? as u64;
        total_archive_size += data_len;
        if big_data_size > data_len as usize {
            read_buffer.resize(data_len as usize, 0);
        }
        output_file.write(&read_buffer)?;
        pb.set_position(decoder.total_in());
        if decoder.total_in() == bz_size {
            decoder.flush()?;
            output_file.flush()?;
            break;
        }
    }
    pb.finish();
    Ok((total_archive_size, new_filename.to_string()))
}

pub fn decompress_tar(path: &str, initial_size: u64) -> Result<(), Error> {
    let tar = File::open(path)?;
    let mut archive = Archive::new(tar);

    let pb = ProgressBar::new(initial_size);
    pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.white/blue}] {bytes}/{total_bytes} ({eta})")
    ?.progress_chars(""));

    pb.set_message(format!("Unpacking to folder"));
    for entry in archive.entries()? 
    {
        let mut entry_value = entry?;
        entry_value.unpack_in(".")?;
        pb.set_position(entry_value.raw_header_position() as u64);
    }

    pb.finish();
    Ok(())
}