quick_fetcher 0.4.3

Asynchronous HTTP file downloader with support for concurrency
Documentation
use crate::error::ArchiveError;
use std::{
    fs::File,
    io::{Read, Seek, SeekFrom, Write},
    path::PathBuf,
};

pub enum ArchiveFormat {
    Tar,
    TarBz2,
    TarGz,
    TarXz,
    TarZst,
    Zip,
    Xz,
    Gz,
    Bz2,
    Zst,
}

impl<'a> ArchiveFormat {
    pub fn decompress(&self, file: File, path: Option<PathBuf>, data: &'a mut [&'a [u8]]) -> Result<(), ArchiveError> {
        log::debug!("Decompressing archive");
        let path = || path.unwrap_or(std::env::current_dir().unwrap());
        let mut archive_output = if matches!(self, Self::Tar | Self::TarBz2 | Self::TarGz | Self::TarXz | Self::TarZst) {
            Output::Tarball(Vec::new())
        } else {
            Output::File(file)
        };
        let reader = SliceReader::new(data);
        match self {
            Self::Bz2 | Self::TarBz2 => {
                let mut decompressor = bzip2::read::BzDecoder::new(reader);
                std::io::copy(&mut decompressor, &mut archive_output)?;
            }
            Self::Gz | Self::TarGz => {
                let mut decompressor = flate2::read::GzDecoder::new(reader);
                std::io::copy(&mut decompressor, &mut archive_output)?;
            }
            Self::Xz | Self::TarXz => {
                let mut decompressor = liblzma::read::XzDecoder::new(reader);
                std::io::copy(&mut decompressor, &mut archive_output)?;
            }
            Self::Zst | Self::TarZst => {
                let mut decompressor = zstd::stream::Decoder::new(reader)?;
                std::io::copy(&mut decompressor, &mut archive_output)?;
            }
            Self::Zip => {
                let path = path();
                let mut archive = zip::ZipArchive::new(reader).map_err(|_| ArchiveError::UnarchiveError)?;
                for i in 0..archive.len() {
                    let mut file = archive.by_index(i).map_err(|_| ArchiveError::UnarchiveError)?;
                    let mut output = File::create(&path.join(file.name()))?;
                    std::io::copy(&mut file, &mut output)?;
                }
                return Ok(());
            }
            _ => (),
        }
        if let Output::Tarball(data) = archive_output {
            let mut archive = tar::Archive::new(data.as_slice());
            archive.unpack(path())?;
        }
        Ok(())
    }
}

struct SliceReader<'a> {
    slices: &'a mut [&'a [u8]],
    index: usize,
    inner_index: usize,
}

impl<'a> SliceReader<'a> {
    fn new(slices: &'a mut [&'a [u8]]) -> Self {
        Self { slices, index: 0, inner_index: 0 }
    }
}

impl<'a> Read for SliceReader<'a> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        while self.index < self.slices.len() {
            match self.slices[self.index].read(buf) {
                Ok(0) => self.index += 1,
                r => return r,
            }
        }
        Ok(0)
    }
}

impl<'a> Seek for SliceReader<'a> {
    fn seek(&mut self, seek: SeekFrom) -> std::io::Result<u64> {
        let pos = match seek {
            SeekFrom::Start(offset) => offset as usize,
            SeekFrom::End(offset) => self.slices.iter().map(|s| s.len()).sum::<usize>() - offset as usize,
            SeekFrom::Current(offset) => (self.index + self.inner_index) + offset as usize,
        };

        self.index = 0;
        self.inner_index = 0;
        let mut total = 0;

        for (i, slice) in self.slices.iter().enumerate() {
            if total + slice.len() > pos {
                self.index = i;
                self.inner_index = pos - total;
                return Ok(pos as u64);
            }
            total += slice.len();
        }

        Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid seek"))
    }
}

enum Output {
    Tarball(Vec<u8>),
    File(File),
}

impl Write for Output {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        match self {
            Self::Tarball(data) => data.write(buf),
            Self::File(file) => file.write(buf),
        }
    }

    fn flush(&mut self) -> std::io::Result<()> {
        match self {
            Self::Tarball(data) => data.flush(),
            Self::File(file) => file.flush(),
        }
    }
}