use crate::manifest::{Artifact, verify_artifact}; use anyhow::{Context, Result}; use std::{ fs, path::{Path, PathBuf}, }; #[derive(Clone, Debug)] pub struct UpdateTransaction { pub root: PathBuf, pub staging: PathBuf, pub lock_file: PathBuf, } impl UpdateTransaction { pub fn new(root: impl Into) -> Self { let root = root.into(); Self { staging: root.join(".staging"), lock_file: root.join("update.lock"), root, } } pub fn from_paths(paths: &iota_paths::IotaPaths) -> Result { Ok(Self { root: paths.install_root.clone(), staging: paths.update_staging_dir(), lock_file: paths.update_lock_file().map_err(|e| anyhow::anyhow!(e))?, }) } pub fn acquire(&self) -> Result { if let Some(parent) = self.lock_file.parent() { fs::create_dir_all(parent)?; } let file = fs::OpenOptions::new() .write(true) .create_new(true) .open(&self.lock_file) .context("update already in progress")?; Ok(file) } pub fn stage_artifact(&self, source: &Path, artifact: &Artifact) -> Result { fs::create_dir_all(&self.staging)?; let target = self.staging.join(&artifact.path); if let Some(parent) = target.parent() { fs::create_dir_all(parent)?; } fs::copy(source, &target)?; verify_artifact(&target, artifact)?; Ok(target) } pub fn activate(&self, version: &str) -> Result<()> { let version_dir = self.root.join("versions").join(version); fs::create_dir_all(version_dir.parent().unwrap())?; fs::rename(&self.staging, &version_dir).context("activate staged release")?; let current_tmp = self.root.join("current.new"); let _ = fs::remove_file(¤t_tmp); std::os::unix::fs::symlink(&version_dir, ¤t_tmp)?; fs::rename(current_tmp, self.root.join("current"))?; Ok(()) } pub fn rollback(&self, previous: &str) -> Result<()> { let current = self.root.join("current"); let tmp = self.root.join("current.rollback"); let _ = fs::remove_file(&tmp); std::os::unix::fs::symlink(self.root.join("versions").join(previous), &tmp)?; fs::rename(tmp, current)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn lock_is_exclusive_and_activation_switches_current() { let dir = tempfile::tempdir().unwrap(); let tx = UpdateTransaction::new(dir.path()); let lock = tx.acquire().unwrap(); assert!(tx.acquire().is_err()); drop(lock); std::fs::create_dir_all(&tx.staging).unwrap(); std::fs::write(tx.staging.join("manifest.json"), b"ok").unwrap(); tx.activate("1.0.0").unwrap(); assert_eq!( std::fs::read_to_string(dir.path().join("current/manifest.json")).unwrap(), "ok" ); } }