use std::fs;
use std::io;
use std::path::Path;
use flate2::Compression;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use tar::{Archive, Builder};
pub fn create_tarball(
config_path: &Path,
db_snapshot_path: &Path,
output_path: &Path,
) -> io::Result<()> {
let output_file = fs::File::create(output_path)?;
let gz = GzEncoder::new(output_file, Compression::default());
let mut archive = Builder::new(gz);
let config_name = config_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("sendword.toml");
archive.append_path_with_name(config_path, config_name)?;
archive.append_path_with_name(db_snapshot_path, "sendword.db")?;
archive.into_inner()?.finish()?;
Ok(())
}
pub fn extract_tarball(tarball_path: &Path, output_dir: &Path) -> io::Result<()> {
fs::create_dir_all(output_dir)?;
let file = fs::File::open(tarball_path)?;
let gz = GzDecoder::new(file);
let mut archive = Archive::new(gz);
archive.unpack(output_dir)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_and_extract_roundtrip() {
let tmp = tempfile::TempDir::new().expect("temp dir");
let config_path = tmp.path().join("sendword.toml");
let db_path = tmp.path().join("sendword.db");
fs::write(&config_path, b"[server]\nport = 8080\n").expect("write config");
fs::write(&db_path, b"SQLITE_FAKE_DB").expect("write db");
let tarball_path = tmp.path().join("backup.tar.gz");
create_tarball(&config_path, &db_path, &tarball_path).expect("create tarball");
assert!(tarball_path.exists(), "tarball should be created");
let extract_dir = tmp.path().join("extracted");
extract_tarball(&tarball_path, &extract_dir).expect("extract tarball");
let extracted_config = fs::read(extract_dir.join("sendword.toml")).expect("read config");
assert_eq!(extracted_config, b"[server]\nport = 8080\n");
let extracted_db = fs::read(extract_dir.join("sendword.db")).expect("read db");
assert_eq!(extracted_db, b"SQLITE_FAKE_DB");
}
#[test]
fn extract_handles_missing_json_config() {
let tmp = tempfile::TempDir::new().expect("temp dir");
let config_path = tmp.path().join("sendword.toml");
let db_path = tmp.path().join("sendword.db");
fs::write(&config_path, b"[server]\nport = 9090\n").expect("write config");
fs::write(&db_path, b"DB_BYTES").expect("write db");
let tarball_path = tmp.path().join("no-json-backup.tar.gz");
create_tarball(&config_path, &db_path, &tarball_path).expect("create tarball");
let extract_dir = tmp.path().join("extracted");
let result = extract_tarball(&tarball_path, &extract_dir);
assert!(
result.is_ok(),
"extraction should succeed without sendword.json: {result:?}"
);
assert!(
extract_dir.join("sendword.toml").exists(),
"sendword.toml should be extracted"
);
assert!(
extract_dir.join("sendword.db").exists(),
"sendword.db should be extracted"
);
assert!(
!extract_dir.join("sendword.json").exists(),
"sendword.json should not exist in this archive"
);
}
}