rustlite_snapshot/
manager.rs

1//! Snapshot manager implementation details
2//!
3//! This module contains internal implementation details for snapshot management.
4
5use crate::{SnapshotMeta, SnapshotType};
6use std::path::Path;
7
8/// Snapshot manager implementation details
9pub struct SnapshotManagerImpl {
10    /// Base path for snapshots
11    base_path: std::path::PathBuf,
12}
13
14impl SnapshotManagerImpl {
15    /// Create a new snapshot manager implementation
16    pub fn new(base_path: impl AsRef<Path>) -> Self {
17        Self {
18            base_path: base_path.as_ref().to_path_buf(),
19        }
20    }
21
22    /// Check if a snapshot needs to be incremental
23    pub fn should_be_incremental(&self, parent: Option<&SnapshotMeta>) -> bool {
24        parent.is_some()
25    }
26
27    /// Calculate the diff between two snapshots
28    pub fn calculate_diff<'a>(
29        &self,
30        old: &'a SnapshotMeta,
31        new_files: &'a [crate::SnapshotFile],
32    ) -> Vec<&'a crate::SnapshotFile> {
33        // Find files that have changed
34        new_files
35            .iter()
36            .filter(|new_file| {
37                // Check if file exists in old snapshot with different checksum
38                old.files
39                    .iter()
40                    .find(|old_file| old_file.relative_path == new_file.relative_path)
41                    .map(|old_file| old_file.checksum != new_file.checksum)
42                    .unwrap_or(true) // New file
43            })
44            .collect()
45    }
46
47    /// Get the base path
48    pub fn base_path(&self) -> &Path {
49        &self.base_path
50    }
51}
52
53/// Snapshot chain for incremental backups
54pub struct SnapshotChain {
55    /// Chain of snapshots from oldest to newest
56    snapshots: Vec<SnapshotMeta>,
57}
58
59impl SnapshotChain {
60    /// Create a new empty chain
61    pub fn new() -> Self {
62        Self {
63            snapshots: Vec::new(),
64        }
65    }
66
67    /// Add a snapshot to the chain
68    pub fn add(&mut self, snapshot: SnapshotMeta) {
69        self.snapshots.push(snapshot);
70    }
71
72    /// Get the latest snapshot
73    pub fn latest(&self) -> Option<&SnapshotMeta> {
74        self.snapshots.last()
75    }
76
77    /// Get the full chain for recovery
78    pub fn chain(&self) -> &[SnapshotMeta] {
79        &self.snapshots
80    }
81
82    /// Check if the chain is valid (no gaps)
83    pub fn is_valid(&self) -> bool {
84        if self.snapshots.is_empty() {
85            return true;
86        }
87
88        // First snapshot must be full
89        if self.snapshots[0].snapshot_type != SnapshotType::Full {
90            return false;
91        }
92
93        // Each incremental must reference the previous
94        for i in 1..self.snapshots.len() {
95            if self.snapshots[i].snapshot_type == SnapshotType::Incremental
96                && self.snapshots[i].parent_id.as_ref() != Some(&self.snapshots[i - 1].id)
97            {
98                return false;
99            }
100        }
101
102        true
103    }
104}
105
106impl Default for SnapshotChain {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_snapshot_chain_empty() {
118        let chain = SnapshotChain::new();
119        assert!(chain.is_valid());
120        assert!(chain.latest().is_none());
121    }
122
123    #[test]
124    fn test_snapshot_chain_with_full() {
125        let mut chain = SnapshotChain::new();
126        chain.add(SnapshotMeta {
127            id: "snap_1".to_string(),
128            timestamp: 1000,
129            path: "/backup/snap_1".to_string(),
130            source_path: "/db".to_string(),
131            sequence: 100,
132            files: vec![],
133            total_size: 0,
134            snapshot_type: SnapshotType::Full,
135            parent_id: None,
136        });
137
138        assert!(chain.is_valid());
139        assert_eq!(chain.latest().unwrap().id, "snap_1");
140    }
141}