use std::fs;
use std::io::{Cursor, Read};
use std::path::Path;
use tar::Archive;
use zip::ZipArchive;
use crate::pg_errors::{Error, Result};
pub async fn unpack_postgres(zip_file_path: &Path, cache_dir: &Path) -> Result<()> {
let zip_file_path = zip_file_path.to_path_buf();
let cache_dir = cache_dir.to_path_buf();
tokio::task::spawn_blocking(move || unpack_postgres_blocking(&zip_file_path, &cache_dir))
.await
.map_err(|e| Error::PgError(e.to_string(), "spawn_blocking join error".into()))?
}
fn unpack_postgres_blocking(zip_file_path: &Path, cache_dir: &Path) -> Result<()> {
let zip_file =
fs::File::open(zip_file_path).map_err(|e| Error::ReadFileError(e.to_string()))?;
let mut jar_archive =
ZipArchive::new(zip_file).map_err(|_| Error::InvalidPgPackage)?;
for i in 0..jar_archive.len() {
let mut file = jar_archive
.by_index(i)
.map_err(|_| Error::InvalidPgPackage)?;
if file.name().ends_with(".txz") || file.name().ends_with(".xz") {
let mut xz_content = Vec::with_capacity(file.compressed_size() as usize);
file.read_to_end(&mut xz_content)
.map_err(|e| Error::ReadFileError(e.to_string()))?;
let mut tar_content = Vec::new();
lzma_rs::xz_decompress(&mut Cursor::new(&xz_content), &mut tar_content)
.map_err(|_| Error::UnpackFailure)?;
Archive::new(Cursor::new(tar_content))
.unpack(cache_dir)
.map_err(|_| Error::UnpackFailure)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
use zip::write::{SimpleFileOptions, ZipWriter};
#[tokio::test]
async fn test_unpack_postgres() -> Result<()> {
let temp_dir = tempdir().expect("Failed to create temp dir");
let cache_dir = temp_dir.path().join("cache");
let zip_file_path = temp_dir.path().join("test_archive.zip");
{
let tar_content = create_dummy_tar_content();
let xz_content = compress_with_xz(&tar_content);
let zip_file = File::create(&zip_file_path).expect("Failed to create zip file");
let mut zip_writer = ZipWriter::new(zip_file);
zip_writer
.start_file("postgres-test.txz", SimpleFileOptions::default())
.expect("Failed to start zip entry");
zip_writer
.write_all(&xz_content)
.expect("Failed to write compressed content to zip file");
zip_writer.finish().expect("Failed to finish zip file");
}
let result = unpack_postgres(&zip_file_path, &cache_dir).await;
assert!(result.is_ok(), "unpack_postgres should succeed: {:?}", result);
let unpacked_files: Vec<_> = std::fs::read_dir(&cache_dir)
.expect("Failed to read unpacked directory")
.collect();
assert!(
!unpacked_files.is_empty(),
"cache_dir should contain the unpacked files"
);
Ok(())
}
fn create_dummy_tar_content() -> Vec<u8> {
let mut tar_data = Vec::new();
{
let mut ar = tar::Builder::new(&mut tar_data);
let content = b"Hello, Postgres!";
let mut header = tar::Header::new_gnu();
header.set_size(content.len() as u64);
header.set_cksum();
ar.append_data(&mut header, "dummy_file.txt", &content[..])
.expect("Failed to add file to tar");
}
tar_data
}
fn compress_with_xz(data: &[u8]) -> Vec<u8> {
let mut compressed = Vec::new();
lzma_rs::xz_compress(&mut Cursor::new(data), &mut compressed)
.expect("Failed to compress data with xz");
compressed
}
}