storage/bundle/
manifest.rs1use std::path::Path;
11
12use crate::error::StorageError;
13use crate::vfs::Vfs;
14
15use super::files::{count_files, count_records, head_of};
16use super::history::Carried;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Manifest {
22 pub(crate) records: u64,
23 pub(crate) head: String,
24 pub(crate) cas_objects: u64,
25 pub(crate) files: u64,
26 pub(crate) history: Carried,
27}
28
29impl Manifest {
30 pub(crate) fn of(
46 vfs: &dyn Vfs,
47 ledger_dir: &Path,
48 cas_dir: &Path,
49 files_root: &Path,
50 ) -> Result<Manifest, StorageError> {
51 Ok(Manifest {
52 records: count_records(vfs, ledger_dir)?,
53 head: head_of(vfs, ledger_dir)?,
54 cas_objects: count_files(vfs, cas_dir)?,
55 files: count_files(vfs, files_root)?,
56 history: Carried::default(),
57 })
58 }
59
60 #[must_use]
62 pub fn records(&self) -> u64 {
63 self.records
64 }
65
66 #[must_use]
69 pub fn head(&self) -> &str {
70 &self.head
71 }
72
73 #[must_use]
74 pub fn cas_objects(&self) -> u64 {
75 self.cas_objects
76 }
77
78 #[must_use]
80 pub fn files(&self) -> u64 {
81 self.files
82 }
83
84 pub(crate) fn to_json(&self) -> String {
85 let mut map = serde_json::Map::new();
86 map.insert("records".to_owned(), self.records.into());
87 map.insert(
88 "head".to_owned(),
89 serde_json::Value::String(self.head.clone()),
90 );
91 map.insert("cas_objects".to_owned(), self.cas_objects.into());
92 map.insert("files".to_owned(), self.files.into());
93 map.insert("history_packs".to_owned(), self.history.packs.into());
94 map.insert("history_refs".to_owned(), self.history.refs.into());
95 serde_json::Value::Object(map).to_string()
96 }
97
98 pub(crate) fn from_json(bytes: &[u8], at: &Path) -> Result<Manifest, StorageError> {
99 let value: serde_json::Value =
100 serde_json::from_slice(bytes).map_err(|err| StorageError::Bundle {
101 op: "read",
102 detail: format!("{}: {err}", at.display()),
103 })?;
104 let number = |key: &str| -> Result<u64, StorageError> {
105 value
106 .get(key)
107 .and_then(serde_json::Value::as_u64)
108 .ok_or_else(|| StorageError::Bundle {
109 op: "read",
110 detail: format!("{} has no {key}", at.display()),
111 })
112 };
113 let carried = |key: &str| match value.get(key) {
114 Some(_) => number(key),
115 None => Ok(0),
116 };
117 Ok(Manifest {
118 records: number("records")?,
119 head: value
120 .get("head")
121 .and_then(serde_json::Value::as_str)
122 .ok_or_else(|| StorageError::Bundle {
123 op: "read",
124 detail: format!("{} has no head", at.display()),
125 })?
126 .to_owned(),
127 cas_objects: number("cas_objects")?,
128 files: number("files")?,
129 history: Carried {
132 packs: carried("history_packs")?,
133 refs: carried("history_refs")?,
134 },
135 })
136 }
137}
138
139pub const MANIFEST: &str = "MANIFEST.json";
141
142pub(crate) use kernel::RESERVED_PREFIX as RESERVED;
144pub(crate) use kernel::layout::{CAS_DIR as CAS, LEDGER_DIR as LEDGER};
149
150pub(crate) const CITY: &str = "city";
153
154#[cfg(test)]
155#[allow(
156 clippy::unwrap_used,
157 clippy::expect_used,
158 clippy::panic,
159 clippy::indexing_slicing,
160 reason = "test code"
161)]
162mod tests {
163 use super::super::export::Bundle;
164 use super::super::fixture::city_with;
165 use super::*;
166
167 #[test]
168 fn the_manifest_is_byte_stable_across_exports() {
169 let home = tempfile::tempdir().unwrap();
170 city_with(2, home.path());
171 let first = tempfile::tempdir().unwrap();
172 let second = tempfile::tempdir().unwrap();
173 Bundle::export(home.path(), first.path()).unwrap();
174 Bundle::export(home.path(), second.path()).unwrap();
175 assert_eq!(
176 std::fs::read(first.path().join(MANIFEST)).unwrap(),
177 std::fs::read(second.path().join(MANIFEST)).unwrap()
178 );
179 assert_eq!(
180 Bundle::read_manifest(first.path()).unwrap(),
181 Bundle::read_manifest(second.path()).unwrap()
182 );
183 }
184}