use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MirrorManifest {
pub version: String,
pub created_at: String,
pub stout_version: String,
pub platforms: Vec<String>,
#[serde(default)]
pub formulas: FormulaManifest,
#[serde(default)]
pub casks: CaskManifest,
#[serde(default)]
pub linux_apps: LinuxAppManifest,
#[serde(default)]
pub checksums: HashMap<String, String>,
#[serde(default)]
pub total_size: u64,
#[serde(default)]
pub upstream_signature: Option<UpstreamSignature>,
#[serde(default)]
pub mirror_signature: Option<String>,
#[serde(default)]
pub signed_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpstreamSignature {
pub index_sha256: String,
pub signature: String,
pub signed_at: u64,
pub index_version: String,
pub formula_count: u32,
pub cask_count: u32,
}
impl MirrorManifest {
pub fn new() -> Self {
Self {
version: "1.0".to_string(),
created_at: chrono_lite_now(),
stout_version: env!("CARGO_PKG_VERSION").to_string(),
platforms: Vec::new(),
formulas: FormulaManifest::default(),
casks: CaskManifest::default(),
linux_apps: LinuxAppManifest::default(),
checksums: HashMap::new(),
total_size: 0,
upstream_signature: None,
mirror_signature: None,
signed_at: None,
}
}
pub fn set_upstream_signature(&mut self, sig: UpstreamSignature) {
self.upstream_signature = Some(sig);
}
pub fn has_upstream_signature(&self) -> bool {
self.upstream_signature.is_some()
}
pub fn has_mirror_signature(&self) -> bool {
self.mirror_signature.is_some()
}
pub fn load(path: &Path) -> crate::Result<Self> {
let content = std::fs::read_to_string(path)?;
let manifest = serde_json::from_str(&content)?;
Ok(manifest)
}
pub fn save(&self, path: &Path) -> crate::Result<()> {
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn add_formula(&mut self, name: &str, info: PackageInfo) {
self.formulas.count += 1;
self.formulas.packages.insert(name.to_string(), info);
}
pub fn add_cask(&mut self, token: &str, info: CaskInfo) {
self.casks.count += 1;
self.casks.packages.insert(token.to_string(), info);
}
pub fn get_formula(&self, name: &str) -> Option<&PackageInfo> {
self.formulas.packages.get(name)
}
pub fn get_cask(&self, token: &str) -> Option<&CaskInfo> {
self.casks.packages.get(token)
}
pub fn add_checksum(&mut self, path: &str, checksum: &str) {
self.checksums.insert(path.to_string(), checksum.to_string());
}
}
impl Default for MirrorManifest {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct FormulaManifest {
pub count: usize,
#[serde(default)]
pub packages: HashMap<String, PackageInfo>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CaskManifest {
pub count: usize,
#[serde(default)]
pub packages: HashMap<String, CaskInfo>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct LinuxAppManifest {
pub count: usize,
#[serde(default)]
pub packages: HashMap<String, LinuxAppInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageInfo {
pub version: String,
#[serde(default)]
pub revision: u32,
pub json_path: String,
#[serde(default)]
pub bottles: HashMap<String, BottleInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BottleInfo {
pub path: String,
pub sha256: String,
pub size: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaskInfo {
pub version: String,
pub json_path: String,
#[serde(default)]
pub artifact: Option<ArtifactInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactInfo {
pub path: String,
pub sha256: String,
pub size: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinuxAppInfo {
pub json_path: String,
#[serde(default)]
pub appimage: Option<ArtifactInfo>,
#[serde(default)]
pub flatpak_id: Option<String>,
}
fn chrono_lite_now() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let secs = duration.as_secs();
let days_since_epoch = secs / 86400;
let remaining_secs = secs % 86400;
let hours = remaining_secs / 3600;
let minutes = (remaining_secs % 3600) / 60;
let seconds = remaining_secs % 60;
let years = 1970 + (days_since_epoch / 365);
let day_of_year = days_since_epoch % 365;
let month = (day_of_year / 30).min(11) + 1;
let day = (day_of_year % 30) + 1;
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
years, month, day, hours, minutes, seconds
)
}