use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, anyhow};
use serde::{Deserialize, Serialize};
use crate::models::upstream::Package;
use crate::routines::migrate::MigrationReport;
use crate::routines::migrate::layout::PathRewrite;
use crate::services::storage::rollback_storage::RollbackRecord;
use crate::utils::filesystem::atomic_ops::write_atomic;
use crate::utils::static_paths::UpstreamPaths;
const PACKAGE_STORAGE_VERSION: u32 = 1;
const ROLLBACK_STORAGE_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PackageStorageFile {
version: u32,
packages: Vec<Package>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct RollbackStorageFile {
version: u32,
records: HashMap<String, Vec<RollbackRecord>>,
}
#[derive(Debug, Clone, Deserialize)]
struct LegacyRollbackStorageFile {
version: u32,
records: HashMap<String, RollbackRecord>,
}
pub(in crate::routines::migrate) fn migrate_package_metadata(
paths: &UpstreamPaths,
rewrites: &[PathRewrite],
report: &mut MigrationReport,
) -> Result<Vec<Package>> {
if !paths.config.packages_file.exists() {
return Ok(Vec::new());
}
let json = fs::read_to_string(&paths.config.packages_file).with_context(|| {
format!(
"Failed to read package metadata '{}'",
paths.config.packages_file.display()
)
})?;
if json.trim().is_empty() {
return Ok(Vec::new());
}
let mut storage: PackageStorageFile = serde_json::from_str(&json).with_context(|| {
format!(
"Failed to parse package metadata '{}'",
paths.config.packages_file.display()
)
})?;
if storage.version != PACKAGE_STORAGE_VERSION {
return Err(anyhow!(
"Unsupported package storage version {} in '{}'. Expected version {}.",
storage.version,
paths.config.packages_file.display(),
PACKAGE_STORAGE_VERSION
));
}
let mut changed = false;
for package in &mut storage.packages {
let package_changed = rewrite_package_paths(package, rewrites);
if package_changed {
changed = true;
report.updated_packages += 1;
}
}
if changed {
write_json(&paths.config.packages_file, &storage)?;
}
Ok(storage.packages)
}
pub(in crate::routines::migrate) fn migrate_rollback_metadata(
paths: &UpstreamPaths,
rewrites: &[PathRewrite],
report: &mut MigrationReport,
) -> Result<()> {
let rollback_file = paths.dirs.metadata_dir.join("rollback.json");
if !rollback_file.exists() {
return Ok(());
}
let json = fs::read_to_string(&rollback_file).with_context(|| {
format!(
"Failed to read rollback metadata '{}'",
rollback_file.display()
)
})?;
if json.trim().is_empty() {
return Ok(());
}
let mut storage: RollbackStorageFile = serde_json::from_str(&json)
.or_else(|_| parse_legacy_rollback_storage(&json))
.with_context(|| {
format!(
"Failed to parse rollback metadata '{}'",
rollback_file.display()
)
})?;
if storage.version != ROLLBACK_STORAGE_VERSION {
return Err(anyhow!(
"Unsupported rollback storage version {} in '{}'. Expected version {}.",
storage.version,
rollback_file.display(),
ROLLBACK_STORAGE_VERSION
));
}
let mut changed = false;
for records in storage.records.values_mut() {
for record in records {
if rewrite_package_paths(&mut record.package_snapshot, rewrites) {
changed = true;
report.updated_rollback_records += 1;
}
}
}
if changed {
write_json(&rollback_file, &storage)?;
}
Ok(())
}
fn parse_legacy_rollback_storage(json: &str) -> serde_json::Result<RollbackStorageFile> {
let legacy: LegacyRollbackStorageFile = serde_json::from_str(json)?;
Ok(RollbackStorageFile {
version: legacy.version,
records: legacy
.records
.into_iter()
.map(|(name, record)| (name, vec![record]))
.collect(),
})
}
fn rewrite_package_paths(package: &mut Package, rewrites: &[PathRewrite]) -> bool {
let mut changed = false;
changed |= rewrite_optional_path(&mut package.install_path, rewrites);
changed |= rewrite_optional_path(&mut package.exec_path, rewrites);
changed
}
fn rewrite_optional_path(path: &mut Option<PathBuf>, rewrites: &[PathRewrite]) -> bool {
let Some(current) = path.as_ref() else {
return false;
};
for rewrite in rewrites {
if let Ok(relative) = current.strip_prefix(&rewrite.old) {
*path = Some(rewrite.new.join(relative));
return true;
}
}
false
}
fn write_json<T: Serialize>(path: &Path, value: &T) -> Result<()> {
let json = serde_json::to_string_pretty(value).context("Failed to serialize migration data")?;
write_atomic(path, json.as_bytes())
.with_context(|| format!("Failed to write '{}'", path.display()))
}