use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
use tempfile::TempDir;
use zip::write::SimpleFileOptions;
use crate::error::{ExpriError, Result};
use crate::git::DirtyPaths;
#[derive(Debug)]
pub struct PatchArchive {
pub _temp_dir: TempDir,
pub path: PathBuf,
pub digest: String,
pub size: u64,
pub file_count: usize,
pub deleted_count: usize,
}
pub fn build_patch_archive(repo_root: &Path, dirty: &DirtyPaths) -> Result<PatchArchive> {
let temp_dir = tempfile::Builder::new().prefix("expri-patch-").tempdir()?;
let path = temp_dir.path().join("patch.zip");
let file = File::create(&path).map_err(|source| ExpriError::IoContext {
action: "create",
path: path.display().to_string(),
source,
})?;
let mut archive = zip::ZipWriter::new(file);
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);
let mut deleted = String::new();
for path in &dirty.deleted {
deleted.push_str(&path.to_string_lossy());
deleted.push('\n');
}
archive.start_file(".deleted", options)?;
archive.write_all(deleted.as_bytes())?;
for relative_path in &dirty.files {
archive.start_file(relative_path.to_string_lossy(), options)?;
let absolute_path = repo_root.join(relative_path);
let mut file = File::open(&absolute_path).map_err(|source| ExpriError::IoContext {
action: "open",
path: absolute_path.display().to_string(),
source,
})?;
io::copy(&mut file, &mut archive)?;
}
archive.finish()?;
let (digest, size) = sha256_file(&path)?;
Ok(PatchArchive {
_temp_dir: temp_dir,
path,
digest,
size,
file_count: dirty.files.len(),
deleted_count: dirty.deleted.len(),
})
}
pub fn sha256_file(path: &Path) -> Result<(String, u64)> {
let mut file = File::open(path).map_err(|source| ExpriError::IoContext {
action: "open",
path: path.display().to_string(),
source,
})?;
let mut hasher = Sha256::new();
let mut size = 0;
let mut buffer = [0; 64 * 1024];
loop {
let read = file.read(&mut buffer)?;
if read == 0 {
break;
}
size += read as u64;
hasher.update(&buffer[..read]);
}
Ok((hex_digest(hasher.finalize()), size))
}
fn hex_digest(bytes: impl AsRef<[u8]>) -> String {
use std::fmt::Write as _;
let bytes = bytes.as_ref();
let mut digest = String::with_capacity(bytes.len() * 2);
for byte in bytes {
write!(&mut digest, "{byte:02x}").expect("write to string");
}
digest
}