/// Planning and applying incremental synchronization.
fn index_of_digest(digests : Array[String], digest : String) -> Int? {
for i, candidate in digests {
if digest_equal(candidate, digest) { return Some(i) }
}
None
}
pub fn diff_manifests(old : Manifest, next : Manifest) -> SyncPlan {
let operations = []
let old_digests = manifest_digests(old)
let old_refs = old.references()
let next_refs = next.references()
for reference in next_refs {
match index_of_digest(old_digests, reference.id()) {
Some(_) => operations.push(Reuse(digest=reference.id(), size=reference.length()))
None => operations.push(Upload(digest=reference.id(), size=reference.length(), source_index=reference.at()))
}
}
let retained = []
for reference in next_refs { retained.push(reference.id()) }
for reference in old_refs {
if index_of_digest(retained, reference.id()) is None {
operations.push(Remove(digest=reference.id()))
}
}
SyncPlan::new(operations)
}
pub fn sync_savings(plan : SyncPlan, original_size : Int) -> Int {
if original_size <= 0 { return 0 }
original_size - plan.uploaded()
}
pub fn sync_ratio(plan : SyncPlan, original_size : Int) -> Double {
if original_size <= 0 { return 0.0 }
plan.uploaded().to_double() / original_size.to_double()
}
pub(all) struct ChunkStore {
mut digests : Array[String]
mut blocks : Array[Array[Byte]]
mut inserted : Int
mut hits : Int
}
pub fn ChunkStore::new() -> ChunkStore {
{ digests: [], blocks: [], inserted: 0, hits: 0 }
}
pub fn ChunkStore::len(self : ChunkStore) -> Int { self.digests.length() }
pub fn ChunkStore::insertions(self : ChunkStore) -> Int { self.inserted }
pub fn ChunkStore::hits(self : ChunkStore) -> Int { self.hits }
pub fn ChunkStore::has(self : ChunkStore, digest : String) -> Bool {
index_of_digest(self.digests, digest) is Some(_)
}
pub fn ChunkStore::put(self : ChunkStore, chunk : Chunk) -> Bool {
match index_of_digest(self.digests, chunk.id()) {
Some(index) => {
self.hits = self.hits + 1
self.blocks[index] = chunk.bytes()
false
}
None => {
self.digests.push(chunk.id())
self.blocks.push(chunk.bytes())
self.inserted = self.inserted + 1
true
}
}
}
pub fn ChunkStore::get(self : ChunkStore, digest : String) -> Array[Byte]? {
match index_of_digest(self.digests, digest) {
Some(index) => Some(self.blocks[index].copy())
None => None
}
}
pub fn ChunkStore::remove(self : ChunkStore, digest : String) -> Bool {
match index_of_digest(self.digests, digest) {
Some(index) => {
let _ = self.digests.remove(index)
let _ = self.blocks.remove(index)
true
}
None => false
}
}
pub fn ChunkStore::clear(self : ChunkStore) -> Unit {
self.digests.clear()
self.blocks.clear()
self.inserted = 0
self.hits = 0
}
pub fn ChunkStore::all_digests(self : ChunkStore) -> Array[String] {
self.digests.copy()
}
pub fn store_chunks(store : ChunkStore, chunks : Array[Chunk]) -> Int {
let mut added = 0
for chunk in chunks { if store.put(chunk) { added = added + 1 } }
added
}
pub fn restore_manifest(store : ChunkStore, manifest : Manifest) -> Result[Array[Byte], String] {
let result = []
for reference in manifest.references() {
match store.get(reference.id()) {
Some(bytes) => {
if bytes.length() != reference.length() { return Err("stored chunk size mismatch") }
if !digest_equal(digest_bytes(bytes), reference.id()) { return Err("stored chunk digest mismatch") }
for byte in bytes { result.push(byte) }
}
None => return Err("required chunk is missing from store")
}
}
if result.length() != manifest.size() { return Err("restored file size mismatch") }
Ok(result)
}
pub fn apply_plan(
old_store : ChunkStore,
new_store : ChunkStore,
old_manifest : Manifest,
new_manifest : Manifest,
) -> Result[Array[Byte], String] {
let _ = diff_manifests(old_manifest, new_manifest)
for reference in new_manifest.references() {
if !new_store.has(reference.id()) {
match old_store.get(reference.id()) {
Some(bytes) => {
new_store.digests.push(reference.id())
new_store.blocks.push(bytes)
}
None => return Err("plan refers to a chunk unavailable in both stores")
}
}
}
restore_manifest(new_store, new_manifest)
}
pub fn plan_reuses(plan : SyncPlan) -> Int {
let mut count = 0
for op in plan.operations() {
match op {
Reuse(..) => count = count + 1
_ => ()
}
}
count
}
pub fn plan_uploads(plan : SyncPlan) -> Int {
let mut count = 0
for op in plan.operations() {
match op {
Upload(..) => count = count + 1
_ => ()
}
}
count
}
pub fn plan_removals(plan : SyncPlan) -> Int {
let mut count = 0
for op in plan.operations() {
match op {
Remove(..) => count = count + 1
_ => ()
}
}
count
}