Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/grain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,19 @@ impl Grain {
}
}

/// The grain's size and how many chunks it covers, without loading it:
/// the commit when it still describes the file, otherwise one walk. Opens
/// nothing for writing, so a caller that may only read the store can use it.
pub fn coverage(dir: &Path, name: &str) -> Option<(u64, usize)> {
let f = File::open(format::grain_path(dir, name)).ok()?;
let s = stamp(&f).ok()??;
let tail = match load_commit(dir, name, &s) {
Some(t) => t,
None => walk(&f, s.first, s.len, u64::MAX).ok()?,
};
Some((s.len, tail.count as usize))
}

pub fn load(path: &Path) -> anyhow::Result<Grain> {
let buf = fs::read(path).with_context(|| format!("reading grain index {}", path.display()))?;
let Some(first) = first_record_offset(&buf) else {
Expand Down Expand Up @@ -1104,4 +1117,46 @@ mod tests {
}
assert!(!g.may_contain_all(0, &marker(0)));
}

#[test]
fn coverage_agrees_with_loading_the_grain() {
let d = TempDir::new();
let _st = a_store(d.path(), 7);
extend_grain(d.path(), "a.log").unwrap();
let loaded = load(&format::grain_path(d.path(), "a.log")).unwrap();
let len = grain_bytes(d.path()).len() as u64;

assert_eq!(
coverage(d.path(), "a.log"),
Some((len, loaded.chunk_count()))
);
// Without the commit it walks, and gets the same answer without
// writing one: the caller may be unable to write the store at all.
fs::remove_file(format::grain_commit_path(d.path(), "a.log")).unwrap();
assert_eq!(coverage(d.path(), "a.log"), Some((len, 7)));
assert!(!format::grain_commit_path(d.path(), "a.log").exists());
}

#[test]
fn coverage_does_not_count_a_torn_tail_or_a_missing_grain() {
let d = TempDir::new();
assert_eq!(coverage(d.path(), "a.log"), None);
let _st = a_store(d.path(), 4);
extend_grain(d.path(), "a.log").unwrap();
let g = OpenOptions::new()
.append(true)
.open(format::grain_path(d.path(), "a.log"))
.unwrap();
(&g).write_all(&100u32.to_le_bytes()).unwrap();
drop(g);
fs::remove_file(format::grain_commit_path(d.path(), "a.log")).unwrap();
assert_eq!(coverage(d.path(), "a.log").map(|c| c.1), Some(4));

fs::write(
format::grain_path(d.path(), "a.log"),
b"not a grain at all, no",
)
.unwrap();
assert_eq!(coverage(d.path(), "a.log"), None);
}
}
7 changes: 1 addition & 6 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2682,12 +2682,7 @@ pub fn summarize_store(
let dropped = std::fs::File::open(&rings_path)
.and_then(|f| format::read_header_dropped(&f))
.unwrap_or_default();
let gpath = format::grain_path(dir, name);
let grain = std::fs::metadata(&gpath).ok().and_then(|m| {
crate::grain::load(&gpath)
.ok()
.map(|g| (m.len(), g.chunk_count()))
});
let grain = crate::grain::coverage(dir, name);
let get = |k: &str| {
bark.and_then(|b| b.get(k))
.and_then(|v| v.as_str())
Expand Down
Loading