1use std::ops::RangeInclusive;
2
3use graft_core::{
4 VolumeId,
5 lsn::{LSN, LSNRangeExt},
6 volume_ref::VolumeRef,
7};
8use smallvec::SmallVec;
9
10#[derive(Clone, Hash)]
13pub struct Snapshot {
14 path: SmallVec<[VolumeRangeRef; 1]>,
15}
16
17#[derive(Clone, PartialEq, Eq, Hash)]
19pub struct VolumeRangeRef {
20 pub vid: VolumeId,
21 pub lsns: RangeInclusive<LSN>,
22}
23
24impl std::fmt::Debug for VolumeRangeRef {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "{:?}[{}]", self.vid, self.lsns.to_string())
27 }
28}
29
30impl VolumeRangeRef {
31 pub fn start_ref(&self) -> VolumeRef {
32 VolumeRef::new(self.vid.clone(), *self.lsns.start())
33 }
34
35 pub fn end_ref(&self) -> VolumeRef {
36 VolumeRef::new(self.vid.clone(), *self.lsns.end())
37 }
38}
39
40impl Snapshot {
41 pub const EMPTY: Self = Self { path: SmallVec::new_const() };
42
43 pub fn new(vid: VolumeId, lsns: RangeInclusive<LSN>) -> Self {
44 assert!(!lsns.is_empty());
45 Self {
46 path: SmallVec::from_const([VolumeRangeRef { vid, lsns }]),
47 }
48 }
49
50 pub fn head(&self) -> Option<(&VolumeId, LSN)> {
51 self.path
52 .first()
53 .map(|entry| (&entry.vid, *entry.lsns.end()))
54 }
55
56 pub fn is_empty(&self) -> bool {
57 self.path.is_empty()
58 }
59
60 pub fn append(&mut self, vid: VolumeId, lsns: RangeInclusive<LSN>) {
61 assert!(!lsns.is_empty());
62 self.path.push(VolumeRangeRef { vid, lsns });
63 }
64
65 pub fn iter(&self) -> std::slice::Iter<'_, VolumeRangeRef> {
67 self.path.iter()
68 }
69}
70
71impl IntoIterator for Snapshot {
72 type Item = VolumeRangeRef;
73 type IntoIter = smallvec::IntoIter<[VolumeRangeRef; 1]>;
74 fn into_iter(self) -> Self::IntoIter {
75 self.path.into_iter()
76 }
77}
78
79impl std::fmt::Debug for Snapshot {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_tuple("Snapshot").field(&self.path).finish()
82 }
83}