use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use memmap2::Mmap;
use crate::error::{HoldError, Result};
use crate::state::{METADATA_VERSION, StateMetadata};
#[cfg(test)]
mod tests;
pub fn load_metadata(metadata_path: &Path) -> Result<StateMetadata> {
match load_metadata_inner(metadata_path) {
Ok(metadata) => Ok(metadata),
Err(HoldError::DeserializationError { .. }) => {
eprintln!("⚠️ Detected incompatible metadata format from previous cargo-hold version");
eprintln!(" Automatically resetting metadata to use new format...");
if let Err(remove_err) = fs::remove_file(metadata_path) {
eprintln!(" Warning: Could not remove old metadata file: {remove_err}");
}
Ok(StateMetadata::new())
}
Err(other) => Err(other),
}
}
fn load_metadata_inner(metadata_path: &Path) -> Result<StateMetadata> {
if !metadata_path.exists() {
return Ok(StateMetadata::new());
}
let file = File::open(metadata_path).map_err(|source| HoldError::IoError {
path: metadata_path.to_path_buf(),
source,
})?;
let file_metadata = file.metadata().map_err(|source| HoldError::IoError {
path: metadata_path.to_path_buf(),
source,
})?;
if file_metadata.len() == 0 {
return Ok(StateMetadata::new());
}
let mmap = unsafe { Mmap::map(&file) }.map_err(|source| HoldError::IoError {
path: metadata_path.to_path_buf(),
source,
})?;
let metadata = deserialize_metadata(&mmap[..])?;
if metadata.version > METADATA_VERSION {
return Err(HoldError::ConfigError(format!(
"Metadata version {} is newer than supported version {}. Please update cargo-hold.",
metadata.version, METADATA_VERSION
)));
}
if metadata.version < METADATA_VERSION {
eprintln!(
"⚠️ Metadata version {} is incompatible with current version {}; resetting metadata",
metadata.version, METADATA_VERSION
);
fs::remove_file(metadata_path).map_err(|source| HoldError::IoError {
path: metadata_path.to_path_buf(),
source,
})?;
return Ok(StateMetadata::new());
}
Ok(metadata)
}
fn deserialize_metadata(bytes: &[u8]) -> Result<StateMetadata> {
rkyv::from_bytes::<StateMetadata, rkyv::rancor::BoxedError>(bytes)
.map_err(HoldError::DeserializationError)
}
pub fn save_metadata(metadata: &StateMetadata, metadata_path: &Path) -> Result<()> {
if let Some(parent) = metadata_path.parent() {
fs::create_dir_all(parent)
.map_err(|source| HoldError::CreateMetadataDirError(parent.to_path_buf(), source))?;
}
let bytes = rkyv::to_bytes::<rkyv::rancor::BoxedError>(metadata)
.map_err(|e| HoldError::SerializationError(Box::new(e)))?;
let temp_path = metadata_path.with_extension("tmp");
let mut temp_file = File::create(&temp_path).map_err(|source| HoldError::IoError {
path: temp_path.clone(),
source,
})?;
temp_file
.write_all(&bytes)
.map_err(|source| HoldError::IoError {
path: temp_path.clone(),
source,
})?;
temp_file.sync_all().map_err(|source| HoldError::IoError {
path: temp_path.clone(),
source,
})?;
fs::rename(&temp_path, metadata_path).map_err(|source| HoldError::IoError {
path: metadata_path.to_path_buf(),
source,
})?;
Ok(())
}
pub fn clean_metadata(metadata_path: &Path) -> Result<()> {
if metadata_path.exists() {
fs::remove_file(metadata_path).map_err(|source| HoldError::IoError {
path: metadata_path.to_path_buf(),
source,
})?;
}
Ok(())
}