1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! NodeRecord Store
//!
//! Provides fast lookup storage for node metadata and relationships.
//! Acts as an index into the filesystem Merkle tree.
pub mod node_metadata;
pub mod persistence;
pub use persistence::SledNodeRecordStore;
use crate::error::StorageError;
use crate::store::node_metadata::NodeMetadata;
use crate::tree::node::MerkleNode;
use crate::tree::Tree;
use crate::types::{Hash, NodeID};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
/// Node type enumeration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NodeType {
File { size: u64, content_hash: [u8; 32] },
Directory,
}
/// NodeRecord: Metadata and relationships for a filesystem node
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeRecord {
pub node_id: NodeID,
pub path: PathBuf,
pub node_type: NodeType,
pub children: Vec<NodeID>,
pub parent: Option<NodeID>,
pub frame_set_root: Option<Hash>,
pub metadata: NodeMetadata,
/// Timestamp when this node was tombstoned (Unix seconds), or None if active.
pub tombstoned_at: Option<u64>,
}
/// NodeRecord Store interface
pub trait NodeRecordStore {
fn get(&self, node_id: &NodeID) -> Result<Option<NodeRecord>, StorageError>;
fn put(&self, record: &NodeRecord) -> Result<(), StorageError>;
/// Find a node record by its canonicalized path
///
/// Returns the NodeRecord if found, None if the path is not in the tree.
fn find_by_path(&self, path: &Path) -> Result<Option<NodeRecord>, StorageError>;
/// List all node records in the store.
///
/// Used for status (total count, path breakdown, top paths). Path mappings
/// (e.g. path:...) are not returned; only node records keyed by NodeID.
fn list_all(&self) -> Result<Vec<NodeRecord>, StorageError>;
/// List node records that are not tombstoned (active only).
fn list_active(&self) -> Result<Vec<NodeRecord>, StorageError>;
/// Get node record by path, including tombstoned nodes.
/// Used for restore path resolution. Path key is only removed on purge.
fn get_by_path(&self, path: &Path) -> Result<Option<NodeRecord>, StorageError>;
/// Mark a node as tombstoned. Sets tombstoned_at to current timestamp.
/// Does not tombstone descendants; caller is responsible for cascade.
fn tombstone(&self, node_id: &NodeID) -> Result<NodeRecord, StorageError>;
/// Remove tombstone marker from a node (restore).
fn restore(&self, node_id: &NodeID) -> Result<NodeRecord, StorageError>;
/// Permanently remove a tombstoned node record (compaction).
/// Only succeeds if node is tombstoned and tombstoned_at is older than cutoff.
fn purge(&self, node_id: &NodeID, cutoff: u64) -> Result<(), StorageError>;
/// List all tombstoned node IDs, optionally filtered by age (older_than timestamp).
fn list_tombstoned(&self, older_than: Option<u64>) -> Result<Vec<NodeID>, StorageError>;
/// Flush any buffered writes to disk. Default implementation is a no-op.
fn flush(&self) -> Result<(), StorageError> {
Ok(())
}
}
impl NodeRecord {
/// Convert a MerkleNode to a NodeRecord
///
/// Requires the tree to look up parent relationships.
pub fn from_merkle_node(
node_id: NodeID,
node: &MerkleNode,
tree: &Tree,
) -> Result<Self, StorageError> {
match node {
MerkleNode::File(file) => Ok(NodeRecord {
node_id,
path: file.path.clone(),
node_type: NodeType::File {
size: file.size,
content_hash: file.content_hash,
},
children: vec![],
parent: tree.find_parent(&node_id),
frame_set_root: None,
metadata: file.metadata.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
tombstoned_at: None,
}),
MerkleNode::Directory(dir) => {
let children: Vec<NodeID> = dir.children.iter().map(|(_, node_id)| *node_id).collect();
Ok(NodeRecord {
node_id,
path: dir.path.clone(),
node_type: NodeType::Directory,
children,
parent: tree.find_parent(&node_id),
frame_set_root: None,
metadata: dir.metadata.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
tombstoned_at: None,
})
}
}
}
/// Populate a NodeRecordStore from a Tree
///
/// Converts all nodes in the tree to NodeRecords and stores them.
pub fn populate_store_from_tree(
store: &dyn NodeRecordStore,
tree: &Tree,
) -> Result<(), StorageError> {
for (node_id, node) in &tree.nodes {
let record = Self::from_merkle_node(*node_id, node, tree)?;
store.put(&record)?;
}
Ok(())
}
}