cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
use crate::error::{CobbleError, CobbleResult};
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::{remove_file, File as StdFile};
use std::path::{Path, PathBuf};
use tokio::task;

pub trait ArchivableEntity {
    const MARKER_NAME: &'static str;

    fn path(&self) -> PathBuf;
    fn marker_path(&self) -> PathBuf;
}

pub async fn archive_entity<'a, T>(
    entity: T,
    destination: impl AsRef<Path>,
    compression: u32,
) -> CobbleResult<()>
where
    T: ArchivableEntity + Send + Sync + 'static,
    T: serde::Serialize,
{
    let destination = PathBuf::from(destination.as_ref());
    task::spawn_blocking(move || archive_entity_blocking(entity, destination, compression)).await?
}

pub async fn unpack_entity_archive<T>(
    archive: impl AsRef<Path>,
    destination: impl AsRef<Path>,
) -> CobbleResult<T>
where
    T: ArchivableEntity + Send + Sync + 'static,
    T: for<'de> serde::Deserialize<'de>,
{
    let archive = PathBuf::from(archive.as_ref());
    let destination = PathBuf::from(destination.as_ref());
    task::spawn_blocking(move || unpack_entity_archive_blocking(archive, destination)).await?
}

fn archive_entity_blocking<T>(
    entity: T,
    destination: impl AsRef<Path>,
    compression: u32,
) -> CobbleResult<()>
where
    T: ArchivableEntity,
    T: serde::Serialize,
{
    trace!("Creating archive file...");
    let archive_file = StdFile::create(&destination)?;
    let encoder = GzEncoder::new(archive_file, Compression::new(compression));
    let mut tar = tar::Builder::new(encoder);

    let src = entity.path();
    trace!("Getting base path for archive...");
    let base = match src.is_dir() {
        true => src
            .file_name()
            .map(|name| name.to_string_lossy().to_string())
            .unwrap_or_default(),
        false => String::default(),
    };

    trace!("Writing entity information...");
    let json_file = StdFile::create(entity.marker_path())?;
    serde_json::to_writer_pretty(json_file, &entity)?;

    trace!("Writing archive...");
    tar.append_dir_all(base, src)?;

    trace!("Flushing archive...");
    let mut encoder = tar.into_inner()?;
    encoder.try_finish()?;

    trace!("Removing entity information from disk...");
    remove_file(entity.marker_path())?;

    Ok(())
}

fn unpack_entity_archive_blocking<T>(
    archive: impl AsRef<Path>,
    destination: impl AsRef<Path>,
) -> CobbleResult<T>
where
    T: ArchivableEntity,
    T: for<'de> serde::Deserialize<'de>,
{
    trace!("Opening archive file...");
    let archive_file = StdFile::open(&archive)?;
    let decoder = GzDecoder::new(archive_file);
    let mut tar = tar::Archive::new(decoder);

    trace!("Searching and parsing entity information...");
    for entry_result in tar.entries()? {
        let entry = entry_result?;
        let path = PathBuf::from(entry.path()?);

        match path.file_name().map(|n| n.to_string_lossy().to_string()) {
            Some(file_name) => {
                if file_name != T::MARKER_NAME {
                    continue;
                }
            }
            None => continue,
        };

        // Parsing information
        let entity = serde_json::from_reader::<_, T>(entry)?;

        trace!("Unpacking archive...");
        let archive_file = StdFile::open(archive)?;
        let decoder = GzDecoder::new(archive_file);
        let mut tar = tar::Archive::new(decoder);
        tar.unpack(&destination)?;

        trace!("Removing entity information from disk...");
        let mut disk_path = PathBuf::from(destination.as_ref());
        disk_path.push(path);
        remove_file(disk_path)?;

        return Ok(entity);
    }

    Err(CobbleError::MissingMarkerFile)
}