use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{Context, Result};
use flate2::Compression;
use flate2::write::GzEncoder;
use serde::Serialize;
use tar::Builder;
use crate::store::Store;
const STASH_SUBDIR: &str = "stashes";
#[derive(Debug, Clone, Serialize)]
pub struct StashReport {
pub archive_path: String,
pub archive_bytes: u64,
pub files: Vec<String>,
pub created_at: i64,
}
pub fn stash(store: &mut Store) -> Result<StashReport> {
store
.conn()
.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")
.context("checkpointing WAL before stash")?;
let stash_dir = store.root().join(STASH_SUBDIR);
fs::create_dir_all(&stash_dir)
.with_context(|| format!("creating stash dir {}", stash_dir.display()))?;
let ts_str: String =
store
.conn()
.query_row("SELECT strftime('%Y%m%dT%H%M%SZ', 'now')", [], |row| {
row.get(0)
})?;
let archive_path = unique_archive_path(&stash_dir, &ts_str);
let db_path = store.db_path();
{
let out = fs::File::create(&archive_path)
.with_context(|| format!("creating archive {}", archive_path.display()))?;
let gz = GzEncoder::new(out, Compression::default());
let mut tar = Builder::new(gz);
tar.append_path_with_name(&db_path, "lantern.db")
.with_context(|| format!("adding {} to archive", db_path.display()))?;
let gz = tar.into_inner()?;
gz.finish()?;
}
let archive_bytes = fs::metadata(&archive_path)
.with_context(|| format!("stat {}", archive_path.display()))?
.len();
Ok(StashReport {
archive_path: archive_path.to_string_lossy().into_owned(),
archive_bytes,
files: vec!["lantern.db".to_string()],
created_at: now_unix(),
})
}
fn unique_archive_path(stash_dir: &std::path::Path, ts_str: &str) -> PathBuf {
let first = stash_dir.join(format!("lantern-{ts_str}.tar.gz"));
if !first.exists() {
return first;
}
let mut n = 1;
loop {
let candidate = stash_dir.join(format!("lantern-{ts_str}-{n}.tar.gz"));
if !candidate.exists() {
return candidate;
}
n += 1;
}
}
fn now_unix() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
pub fn print_text(report: &StashReport) {
println!(
"stashed archive={} bytes={} files={}",
report.archive_path,
report.archive_bytes,
report.files.join(","),
);
}
pub fn print_json(report: &StashReport) -> Result<()> {
println!("{}", serde_json::to_string_pretty(report)?);
Ok(())
}