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,
};
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)
}