/// MoonChunk public data model.
/// A half-open byte interval in the source stream.
pub(all) struct Span {
start : Int
end : Int
} derive(Show, Eq)
pub fn Span::new(start : Int, end : Int) -> Span {
{ start, end }
}
pub fn Span::length(self : Span) -> Int {
self.end - self.start
}
pub fn Span::is_empty(self : Span) -> Bool {
self.start >= self.end
}
/// A content-addressed block. The digest is represented as a stable hex string.
pub(all) struct Chunk {
ordinal : Int
span : Span
digest : String
data : Array[Byte]
} derive(Show)
pub fn Chunk::new(ordinal : Int, span : Span, data : Array[Byte]) -> Chunk {
{
ordinal,
span,
digest: digest_bytes(data),
data,
}
}
pub fn Chunk::size(self : Chunk) -> Int {
self.data.length()
}
pub fn Chunk::id(self : Chunk) -> String {
self.digest
}
pub fn Chunk::bytes(self : Chunk) -> Array[Byte] {
self.data.copy()
}
pub fn Chunk::index(self : Chunk) -> Int {
self.ordinal
}
pub fn Chunk::range(self : Chunk) -> Span {
self.span
}
/// Parameters used by the rolling chunker.
pub(all) struct ChunkerConfig {
min_size : Int
average_size : Int
max_size : Int
seed : UInt
} derive(Show, Eq)
pub fn ChunkerConfig::new(
min_size? : Int = 512,
average_size? : Int = 2048,
max_size? : Int = 8192,
seed? : UInt = 0x9e3779b9U,
) -> ChunkerConfig {
let min_size = if min_size < 16 { 16 } else { min_size }
let average_size = if average_size < min_size { min_size } else { average_size }
let max_size = if max_size < average_size { average_size } else { max_size }
{ min_size, average_size, max_size, seed }
}
pub fn ChunkerConfig::validate(self : ChunkerConfig) -> Result[Unit, String] {
if self.min_size <= 0 {
Err("min_size must be positive")
} else if self.min_size > self.average_size {
Err("min_size must not exceed average_size")
} else if self.average_size > self.max_size {
Err("average_size must not exceed max_size")
} else {
Ok(())
}
}
pub fn ChunkerConfig::with_seed(self : ChunkerConfig, seed : UInt) -> ChunkerConfig {
{ ..self, seed }
}
pub fn ChunkerConfig::minimum(self : ChunkerConfig) -> Int {
self.min_size
}
pub fn ChunkerConfig::average(self : ChunkerConfig) -> Int {
self.average_size
}
pub fn ChunkerConfig::maximum(self : ChunkerConfig) -> Int {
self.max_size
}
/// A reference to a stored chunk inside a manifest.
pub(all) struct ChunkRef {
position : Int
digest : String
size : Int
} derive(Show, Eq)
pub fn ChunkRef::new(position : Int, digest : String, size : Int) -> ChunkRef {
{ position, digest, size }
}
pub fn ChunkRef::at(self : ChunkRef) -> Int { self.position }
pub fn ChunkRef::id(self : ChunkRef) -> String { self.digest }
pub fn ChunkRef::length(self : ChunkRef) -> Int { self.size }
/// An immutable description of one version of a file.
pub(all) struct Manifest {
name : String
total_size : Int
chunks : Array[ChunkRef]
root : String
format_version : Int
} derive(Show)
pub fn Manifest::new(name : String, chunks : Array[ChunkRef]) -> Manifest {
let mut total = 0
for c in chunks { total = total + c.size }
let root = merkle_root(chunks.map(c => c.digest))
{ name, total_size: total, chunks, root, format_version: 1 }
}
pub fn Manifest::filename(self : Manifest) -> String { self.name }
pub fn Manifest::size(self : Manifest) -> Int { self.total_size }
pub fn Manifest::count(self : Manifest) -> Int { self.chunks.length() }
pub fn Manifest::root_digest(self : Manifest) -> String { self.root }
pub fn Manifest::version(self : Manifest) -> Int { self.format_version }
pub fn Manifest::references(self : Manifest) -> Array[ChunkRef] { self.chunks.copy() }
pub fn Manifest::is_valid(self : Manifest) -> Bool {
self.total_size >= 0 && self.chunks.length() >= 0 &&
self.root == merkle_root(self.chunks.map(c => c.digest))
}
/// A single operation in an incremental synchronization plan.
pub(all) enum SyncOp {
Reuse(digest~ : String, size~ : Int)
Upload(digest~ : String, size~ : Int, source_index~ : Int)
Remove(digest~ : String)
} derive(Show, Eq)
pub(all) struct SyncPlan {
operations : Array[SyncOp]
reused_bytes : Int
uploaded_bytes : Int
removed_chunks : Int
} derive(Show)
pub fn SyncPlan::new(operations : Array[SyncOp]) -> SyncPlan {
let mut reused = 0
let mut uploaded = 0
let mut removed = 0
for op in operations {
match op {
Reuse(size~) => reused = reused + size
Upload(size~) => uploaded = uploaded + size
Remove => removed = removed + 1
}
}
{ operations, reused_bytes: reused, uploaded_bytes: uploaded, removed_chunks: removed }
}
pub fn SyncPlan::operations(self : SyncPlan) -> Array[SyncOp] { self.operations.copy() }
pub fn SyncPlan::reused(self : SyncPlan) -> Int { self.reused_bytes }
pub fn SyncPlan::uploaded(self : SyncPlan) -> Int { self.uploaded_bytes }
pub fn SyncPlan::removed(self : SyncPlan) -> Int { self.removed_chunks }
/// A detailed verification result suitable for a CLI or a UI.
pub(all) enum VerifyResult {
Valid
InvalidManifest(reason~ : String)
MissingChunk(digest~ : String, position~ : Int)
CorruptChunk(expected~ : String, actual~ : String, position~ : Int)
SizeMismatch(expected~ : Int, actual~ : Int)
} derive(Show, Eq)
pub fn VerifyResult::ok(self : VerifyResult) -> Bool {
match self {
Valid => true
_ => false
}
}