agent_migrate_core/transaction/
engine.rs1use std::path::Path;
2
3use crate::transaction::conflict::resolve_plan;
4use crate::transaction::filesystem::FileSystem;
5use crate::transaction::snapshot_store::SnapshotStore;
6use crate::transaction::{
7 SnapshotId, TransactionError, TransactionOptions, TransactionResult, WritePlan,
8};
9
10#[derive(Debug, Clone, Copy)]
11pub struct TransactionEngine {
12 private_mode: u32,
13}
14
15impl Default for TransactionEngine {
16 fn default() -> Self {
17 Self {
18 private_mode: 0o600,
19 }
20 }
21}
22
23impl TransactionEngine {
24 pub fn apply<F>(
30 self,
31 plan: &WritePlan,
32 options: &TransactionOptions,
33 doctor: F,
34 ) -> Result<TransactionResult, TransactionError>
35 where
36 F: FnOnce(&Path) -> Result<(), String>,
37 {
38 let filesystem = FileSystem::new(self.private_mode);
39 let resolved = resolve_plan(plan, options.root(), options.conflict_policy())?;
40 preflight(&resolved, options)?;
41 let snapshot_id = SnapshotId::new();
42 if options.is_dry_run() {
43 return Ok(TransactionResult {
44 snapshot_id,
45 dry_run: true,
46 writes: resolved.len(),
47 });
48 }
49 FileSystem::prepare_roots(options)?;
50 let _lock = FileSystem::acquire_lock(options)?;
51 let snapshots = SnapshotStore::new(filesystem, options);
52 snapshots.create(snapshot_id, &resolved)?;
53 for write in resolved.writes() {
54 let target = options.root().join(write.path.as_path());
55 if let Err(error) =
56 filesystem.atomic_write(options.root(), &target, &write.content, write.mode)
57 {
58 return match snapshots.restore(snapshot_id) {
59 Ok(()) => Err(error),
60 Err(rollback) => Err(TransactionError::RollbackFailed {
61 message: format!("{error}; {rollback}"),
62 }),
63 };
64 }
65 }
66 if let Err(message) = doctor(options.root()) {
67 return match snapshots.restore(snapshot_id) {
68 Ok(()) => Err(TransactionError::DoctorFailed { message }),
69 Err(rollback) => Err(TransactionError::RollbackFailed {
70 message: format!("{message}; {rollback}"),
71 }),
72 };
73 }
74 Ok(TransactionResult {
75 snapshot_id,
76 dry_run: false,
77 writes: resolved.len(),
78 })
79 }
80
81 pub fn rollback(
87 self,
88 snapshot_id: SnapshotId,
89 options: &TransactionOptions,
90 ) -> Result<(), TransactionError> {
91 let filesystem = FileSystem::new(self.private_mode);
92 FileSystem::prepare_roots(options)?;
93 let _lock = FileSystem::acquire_lock(options)?;
94 SnapshotStore::new(filesystem, options).restore(snapshot_id)
95 }
96}
97
98fn preflight(plan: &WritePlan, options: &TransactionOptions) -> Result<(), TransactionError> {
99 for write in plan.writes() {
100 FileSystem::ensure_path_safe(options.root(), write.path.as_path())?;
101 }
102 Ok(())
103}