pub mod compute;
pub mod sync;
pub mod types;
pub use compute::{
apply_delta, compute_delta, create_full_insert_delta, create_identity_delta,
generate_signatures, generate_signatures_default, optimal_block_size,
};
pub use sync::{
FileInfo, MemoryFs, SyncFilesystem, apply_file_delta, compute_file_delta,
compute_new_file_delta, estimate_transfer_size, execute_sync, plan_sync,
};
pub use types::{
BlockSignature, DEFAULT_BLOCK_SIZE, Delta, DeltaError, DeltaOp, DeltaResult, MAX_BLOCK_SIZE,
MIN_BLOCK_SIZE, RollingChecksum, SignatureSet, SyncEntry, SyncPlan, SyncProgress, SyncStatus,
};
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn test_exports_accessible() {
let _ = RollingChecksum::new(512);
let _ = DeltaOp::copy(0, 100);
let _ = SyncStatus::New;
let _ = DEFAULT_BLOCK_SIZE;
}
#[test]
fn test_full_workflow() {
let mut original = Vec::with_capacity(8192);
for i in 0..8192 {
original.push((i % 256) as u8);
}
let sigs = generate_signatures(&original, 512).unwrap();
let mut modified = original.clone();
modified[4000..4100].fill(0xFF);
let delta = compute_delta(&sigs, &modified).unwrap();
assert!(!delta.is_full_replace());
let result = apply_delta(&original, &delta).unwrap();
assert_eq!(result, modified);
}
#[test]
fn test_large_file_workflow() {
let mut original = Vec::with_capacity(100_000);
for i in 0..100_000 {
original.push((i % 256) as u8);
}
let mut modified = original.clone();
modified[50000..50100].fill(0xFF);
let block_size = optimal_block_size(original.len() as u64);
let sigs = generate_signatures(&original, block_size).unwrap();
let delta = compute_delta(&sigs, &modified).unwrap();
assert!(delta.copy_percentage() > 90.0);
let result = apply_delta(&original, &delta).unwrap();
assert_eq!(result, modified);
}
#[test]
fn test_sync_workflow() {
let mut source = MemoryFs::new();
let mut dest = MemoryFs::new();
source.add_file("/doc1.txt", b"Document one content", 1000);
source.add_file("/doc2.txt", b"Document two content", 1000);
source.add_file("/new.txt", b"New document", 2000);
dest.add_file("/doc1.txt", b"Document one content", 1000);
dest.add_file("/doc2.txt", b"Old document two", 500);
dest.add_file("/obsolete.txt", b"Delete this", 100);
let source_files = source.list_files("/").unwrap();
let dest_files = dest.list_files("/").unwrap();
let plan = plan_sync("/src", "/dst", &source_files, &dest_files, 0);
assert_eq!(plan.count_by_status(SyncStatus::Unchanged), 1);
assert_eq!(plan.count_by_status(SyncStatus::Modified), 1);
assert_eq!(plan.count_by_status(SyncStatus::New), 1);
assert_eq!(plan.count_by_status(SyncStatus::Deleted), 1);
let progress =
execute_sync(&plan, &source, &mut dest, None::<fn(&SyncProgress)>, 0).unwrap();
assert!(dest.exists("/doc1.txt"));
assert!(dest.exists("/doc2.txt"));
assert!(dest.exists("/new.txt"));
assert!(!dest.exists("/obsolete.txt"));
assert_eq!(
dest.read_file("/doc2.txt").unwrap(),
b"Document two content"
);
}
#[test]
fn test_incremental_updates() {
let mut source = MemoryFs::new();
let mut dest = MemoryFs::new();
source.add_file("/data.bin", &[0u8; 10000], 1000);
let src1 = source.list_files("/").unwrap();
let dst1 = dest.list_files("/").unwrap();
let plan1 = plan_sync("/s", "/d", &src1, &dst1, 0);
execute_sync(&plan1, &source, &mut dest, None::<fn(&SyncProgress)>, 0).unwrap();
let mut new_data = [0u8; 10000];
new_data[5000..5100].fill(0xFF);
source.add_file("/data.bin", &new_data, 2000);
let src2 = source.list_files("/").unwrap();
let dst2 = dest.list_files("/").unwrap();
let plan2 = plan_sync("/s", "/d", &src2, &dst2, 0);
assert_eq!(plan2.count_by_status(SyncStatus::Modified), 1);
let est = estimate_transfer_size(&plan2);
assert!(est < 5000);
execute_sync(&plan2, &source, &mut dest, None::<fn(&SyncProgress)>, 0).unwrap();
let result = dest.read_file("/data.bin").unwrap();
assert_eq!(&result, &new_data);
}
#[test]
fn test_empty_sync() {
let source = MemoryFs::new();
let dest = MemoryFs::new();
let src = source.list_files("/").unwrap();
let dst = dest.list_files("/").unwrap();
let plan = plan_sync("/s", "/d", &src, &dst, 0);
assert!(plan.is_empty());
}
#[test]
fn test_rolling_checksum_api() {
let mut rc = RollingChecksum::new(8);
for &b in b"abcdefgh" {
rc.push(b);
}
let checksum1 = rc.checksum();
rc.roll(b'i');
let checksum2 = rc.checksum();
assert_ne!(checksum1, checksum2);
rc.roll(b'j');
let checksum3 = rc.checksum();
assert_ne!(checksum2, checksum3);
}
#[test]
fn test_delta_statistics() {
let source: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
let mut target = source.clone();
target[5000..5100].fill(0xFF);
let sigs = generate_signatures(&source, 512).unwrap();
let delta = compute_delta(&sigs, &target).unwrap();
assert!(!delta.is_identity());
assert!(!delta.is_full_replace());
assert!(delta.copy_percentage() > 80.0);
}
#[test]
fn test_sync_progress() {
let mut progress = SyncProgress::new(10, 10000, 0);
progress.update("/file1.txt", 1000, 100);
assert_eq!(progress.percentage(), 10.0);
progress.update("/file2.txt", 2000, 200);
assert_eq!(progress.percentage(), 30.0);
assert!(progress.rate(200) > 0.0);
}
}