flop 0.2.7

floppy-disk facades for common archive formats!
Documentation
use std::io::Cursor;
use std::os::unix::prelude::OsStringExt;

use async_zip::tokio::read::seek::ZipFileReader;
use async_zip::tokio::write::ZipFileWriter;
use async_zip::{ZipDateTime, ZipEntryBuilder, ZipString};
use chrono::DateTime;
use smoosh::CompressionType;
use tokio::io::AsyncReadExt;
use tracing::debug;

crate::util::archive_format!(Zip, "a.zip", zip_open, zip_close);

async fn zip_open<P: Into<PathBuf>>(path: P) -> Result<ZipInternalMetadata> {
    let path = path.into();
    if !crate::util::exists_async(path.clone()).await {
        let _archive = ZipFileWriter::with_tokio(tokio::fs::File::create(path).await?);
        return Ok(ZipInternalMetadata {
            delegate: MemFloppyDisk::new(),
            compression: CompressionType::None,
            ordered_paths: IndexSet::new(),
        });
    }

    debug!("opening zip file {}", path.display());
    let mut file = crate::util::async_file(path).await?;
    let mut buffer = vec![];
    let c = smoosh::recompress(&mut file, &mut buffer, smoosh::CompressionType::None).await?;
    let mut archive = ZipFileReader::with_tokio(Cursor::new(buffer))
        .await
        .map_err(fix_err)?;
    let out = MemFloppyDisk::new();
    let mut ordered_paths = IndexSet::new();

    let archive_file = archive.file();
    let entries = archive_file.entries();
    for idx in 0..entries.len() {
        let mut zip_entry = archive.reader_with_entry(idx).await.map_err(fix_err)?;
        let entry = zip_entry.entry();
        let path = PathBuf::from(OsString::from_vec(entry.filename().as_bytes().to_vec()));
        debug!("processing archive path {}", path.display());
        ordered_paths.insert(path.clone());

        if let Some(parent) = path.parent() {
            out.create_dir_all(parent).await?;
        }
        let mut handle = MemOpenOptions::new()
            .create(true)
            .write(true)
            .open(&out, &path)
            .await?;

        let mut data = vec![];
        zip_entry
            .read_to_end_checked(&mut data)
            .await
            .map_err(fix_err)?;
        tokio::io::copy(&mut data.as_slice(), &mut handle).await?;
        debug!("copied path!");
    }

    Ok(ZipInternalMetadata {
        delegate: out,
        compression: c,
        ordered_paths,
    })
}

async fn zip_close(
    disk: &MemFloppyDisk,
    scope: &Path,
    compression: CompressionType,
    ordered_paths: &IndexSet<PathBuf>,
) -> Result<()> {
    debug!("closing zip at {}", scope.display());
    let mut file = tokio::fs::OpenOptions::new()
        .write(true)
        .truncate(true)
        .open(scope)
        .await?;

    let buffer = vec![];
    let mut writer = ZipFileWriter::with_tokio(buffer);

    for path in ordered_paths {
        let metadata = disk.metadata(path).await?;
        if metadata.is_file() {
            debug!("writing path {} to zip!", path.display());
            let mut handle = MemOpenOptions::new().read(true).open(disk, path).await?;

            let mut data = vec![];
            handle.read_to_end(&mut data).await?;

            // TODO: lol not utf8
            let entry = ZipEntryBuilder::new(
                ZipString::new(
                    path.to_string_lossy().to_string().as_bytes().to_vec(),
                    async_zip::StringEncoding::Utf8,
                ),
                async_zip::Compression::Stored,
            );

            let entry = entry
                .last_modification_date(ZipDateTime::from_chrono(&DateTime::from(
                    metadata.modified().unwrap(),
                )))
                .unix_permissions(metadata.permissions().mode() as u16);

            writer
                .write_entry_whole(entry, &data)
                .await
                .map_err(fix_err)?;

            debug!("wrote path!");
        }
    }

    let writer = writer.close().await.map_err(fix_err)?;
    smoosh::recompress(&mut writer.get_ref().as_slice(), &mut file, compression).await?;

    Ok(())
}

fn fix_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> std::io::Error {
    std::io::Error::new(std::io::ErrorKind::Other, err)
}