///|
/// Rebuild a complete set in memory. No output is returned until every
/// reconstructed file has passed its packet's whole-file checksum.
pub fn RecoverySet::repair(
self : RecoverySet,
files : Map[String, Bytes],
) -> Array[(String, Bytes)] raise {
let scan = self.scan(files)
let exps = self.parity.keys().to_array()
exps.sort()
let missing = []
for i = 0; i < scan.blocks.length(); i = i + 1 {
if scan.blocks[i] is None {
missing.push(i)
}
}
guard solve(
scan.blocks.length(),
missing,
exps,
exps.map(fn(e) { self.parity[e] }),
fn(row, offset, j, weight) {
if scan.blocks[j] is Some(view) {
xor_slice(row, offset, view, weight)
}
},
)
is Some(restored) else {
fail("insufficient independent recovery slices")
}
let outputs = []
let mut pos = 0
let mut recovered = 0
for entry in self.members {
let buf = @buffer.Buffer(size_hint=entry.length)
let count = (entry.length + self.width - 1) / self.width
for i = 0; i < count; i = i + 1 {
let block = match scan.blocks[pos] {
Some(known) => known
None => {
let value = restored[recovered]
recovered = recovered + 1
as_bytes(value)[:]
}
}
pos = pos + 1
let len = (entry.length - i * self.width).min(self.width)
buf.write_bytes(block[:len])
}
let bytes = buf.to_bytes()
if md5(bytes) != entry.hash {
fail("restored checksum mismatch: " + entry.name)
}
outputs.push((entry.name, bytes))
}
outputs
}
///|
test "recover missing file and the final partial slice of another" {
let files = [("one", b"abcdefghij"), ("two", b"xyz")]
let s = read_set([create(files, width=4, recovery_blocks=2)])
let outputs = Map(s.repair(Map([("one", b"abcdefghXX")])))
for pair in files {
assert_eq(outputs.get(pair.0), Some(pair.1))
}
let few = read_set([create(files, width=4, recovery_blocks=1)])
assert_true(
try {
ignore(few.repair(Map([("one", b"abcdefghXX")])))
false
} catch {
_ => true
},
)
}