use std::fs;
use std::io::Read;
use std::path::Path;
use anyhow::{Context, Result, bail};
use serde::Deserialize;
use super::{DataStats, SNAPSHOT_FORMAT};
use crate::shared::i18n::Locale;
use crate::shared::text_decode;
const FIRST_COMPARABLE_FORMAT: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OtherCopy {
Archive,
Snapshot,
}
pub fn other_copy(path: &Path, loc: &Locale) -> Result<OtherCopy> {
let mut head = [0u8; 2];
let read = fs::File::open(path)
.and_then(|mut file| file.read(&mut head))
.with_context(|| open_context(path, loc))?;
Ok(if read == head.len() && &head == b"PK" {
OtherCopy::Archive
} else {
OtherCopy::Snapshot
})
}
fn open_context(path: &Path, loc: &Locale) -> String {
loc.tf(
"cli.stats.ctx.open_other",
&[("path", &path.display().to_string())],
)
}
#[derive(Deserialize)]
struct Probe {
format: u32,
}
pub fn read_snapshot(path: &Path, loc: &Locale) -> Result<DataStats> {
let name = path.display().to_string();
let bytes = fs::read(path).with_context(|| open_context(path, loc))?;
let not_a_snapshot = || loc.tf("cli.stats.err.not_a_snapshot", &[("path", &name)]);
let Some(decoded) = text_decode::decode_file(&bytes, false, None) else {
bail!("{}", not_a_snapshot());
};
let text = decoded.text.trim_start_matches('\u{feff}');
let Ok(probe) = serde_json::from_str::<Probe>(text) else {
bail!("{}", not_a_snapshot());
};
let format = probe.format.to_string();
if probe.format < FIRST_COMPARABLE_FORMAT {
bail!(
"{}",
loc.tf(
"cli.stats.err.snapshot_old",
&[("path", &name), ("format", &format)]
)
);
}
if probe.format > SNAPSHOT_FORMAT {
bail!(
"{}",
loc.tf(
"cli.stats.err.snapshot_new",
&[("path", &name), ("format", &format)]
)
);
}
serde_json::from_str(text).with_context(not_a_snapshot)
}