void_core/ops/
diagnostics.rs1use std::collections::HashMap;
4
5use crate::cid;
6use crate::crypto::CommitReader;
7use crate::ContentHash;
8use crate::VoidContext;
9use crate::metadata::{Commit, MetadataBundle};
10use crate::metadata::manifest_tree::TreeManifest;
11
12use crate::store::ObjectStoreExt;
13use crate::Result;
14
15#[derive(Debug, Clone)]
17pub struct ShardDiagnostics {
18 pub file_to_shard: HashMap<String, u64>,
20 pub file_hashes: HashMap<String, ContentHash>,
22 pub shard_cids: HashMap<u64, String>,
24 pub shard_files: HashMap<u64, Vec<String>>,
26 pub shard_sizes: HashMap<u64, u64>,
28 pub dir_listing_counts: HashMap<u64, usize>,
30}
31
32#[derive(Debug, Clone)]
34pub struct ShardDiff {
35 pub new_files: Vec<String>,
36 pub removed_files: Vec<String>,
37 pub moved_files: Vec<(String, u64, u64)>,
38 pub modified_files: Vec<String>,
39}
40
41impl ShardDiagnostics {
42 pub fn from_manifest(manifest: &TreeManifest, metadata: &MetadataBundle) -> Result<Self> {
44 let mut file_to_shard = HashMap::new();
45 let mut file_hashes = HashMap::new();
46 let mut shard_cids: HashMap<u64, String> = HashMap::new();
47 let mut shard_files: HashMap<u64, Vec<String>> = HashMap::new();
48 let mut shard_sizes: HashMap<u64, u64> = HashMap::new();
49
50 for range in &metadata.shard_map.ranges {
52 if let Some(cid_bytes) = range.cid.as_ref() {
53 if let Ok(shard_cid) = crate::cid::from_bytes(cid_bytes.as_bytes()) {
54 shard_cids.insert(range.shard_id, shard_cid.to_string());
55 }
56 shard_sizes.insert(range.shard_id, range.compressed_size);
57 }
58 }
59
60 for (idx, sref) in manifest.shards().iter().enumerate() {
62 shard_sizes.insert(idx as u64, sref.size_compressed);
63 }
64
65 for entry_result in manifest.iter() {
67 let entry = entry_result?;
68 let shard_id = entry.shard_index as u64;
69 file_to_shard.insert(entry.path.clone(), shard_id);
70 file_hashes.insert(entry.path.clone(), entry.content_hash);
71 shard_files.entry(shard_id).or_default().push(entry.path.clone());
72 }
73
74 for files in shard_files.values_mut() {
76 files.sort();
77 }
78
79 Ok(Self {
80 file_to_shard,
81 file_hashes,
82 shard_cids,
83 shard_files,
84 shard_sizes,
85 dir_listing_counts: HashMap::new(), })
87 }
88
89 pub fn from_commit(ctx: &VoidContext, commit: &Commit, metadata: &MetadataBundle, reader: &CommitReader) -> Result<Self> {
91 let store = ctx.open_store()?;
92 let manifest = TreeManifest::from_commit(&store, commit, reader)?
93 .ok_or_else(|| crate::VoidError::NotFound("commit has no manifest".into()))?;
94 Self::from_manifest(&manifest, metadata)
95 }
96
97 pub fn from_metadata(ctx: &VoidContext, metadata: &MetadataBundle, reader: &CommitReader) -> Result<Self> {
101 let store = ctx.open_store()?;
102 let head_cid = crate::refs::resolve_head(&ctx.paths.void_dir)?
104 .ok_or_else(|| crate::VoidError::NotFound("HEAD".into()))?;
105 let commit_cid = cid::from_bytes(head_cid.as_bytes())?;
106 let commit_encrypted: void_crypto::EncryptedCommit = store.get_blob(&commit_cid)?;
107 let (commit_bytes, _) = CommitReader::open_with_vault(&ctx.crypto.vault, &commit_encrypted)?;
108 let commit = commit_bytes.parse()?;
109
110 let manifest = TreeManifest::from_commit(&store, &commit, reader)?
111 .ok_or_else(|| crate::VoidError::NotFound("commit has no manifest".into()))?;
112 Self::from_manifest(&manifest, metadata)
113 }
114
115 pub fn total_files(&self) -> usize {
117 self.file_to_shard.len()
118 }
119
120 pub fn shard_count(&self) -> usize {
122 self.shard_files.len()
123 }
124
125 pub fn diff(&self, next: &Self) -> ShardDiff {
127 let mut new_files = Vec::new();
128 let mut removed_files = Vec::new();
129 let mut moved_files = Vec::new();
130 let mut modified_files = Vec::new();
131
132 for (file, shard_id) in &next.file_to_shard {
133 let prev_id = match self.file_to_shard.get(file) {
134 Some(id) => id,
135 None => {
136 new_files.push(file.clone());
137 continue;
138 }
139 };
140
141 if prev_id != shard_id {
142 moved_files.push((file.clone(), *prev_id, *shard_id));
143 }
144
145 if self
146 .file_hashes
147 .get(file)
148 .zip(next.file_hashes.get(file))
149 .map(|(prev, next)| prev != next)
150 .unwrap_or(false)
151 {
152 modified_files.push(file.clone());
153 }
154 }
155
156 for file in self.file_to_shard.keys() {
157 if !next.file_to_shard.contains_key(file) {
158 removed_files.push(file.clone());
159 }
160 }
161
162 new_files.sort();
163 removed_files.sort();
164 moved_files.sort_by(|a, b| a.0.cmp(&b.0));
165 modified_files.sort();
166
167 ShardDiff {
168 new_files,
169 removed_files,
170 moved_files,
171 modified_files,
172 }
173 }
174}
175