Getting Started
Learn how to make a control file and run a transfer.
Describing a file
ControlFile::generate reads a file once and describes it. The length is
passed in rather than sought for, so the block size can be chosen before the
first block is read.
use zync::{ControlFile, GenerateOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut file = std::fs::File::open("image.bin")?;
let length = file.metadata()?.len();
let options = GenerateOptions {
filename: Some("image.bin"),
urls: vec!["https://example.com/image.bin".to_string()],
..GenerateOptions::default()
};
let control = ControlFile::generate(&mut file, length, &options)?;
let mut out = std::fs::File::create("image.zsync")?;
control.write(&mut out)?;
Ok(())
}Running a transfer
An Assembly owns the transfer. Local copies are submitted first, so the
matcher knows what it already has before anything is fetched.
use std::path::Path;
use zync::{Assembly, ControlFile, GenerateOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut file = std::fs::File::open("image.bin")?;
let length = file.metadata()?.len();
let control = ControlFile::generate(
&mut file,
length,
&GenerateOptions {
urls: vec!["https://example.com/image.bin".to_string()],
..GenerateOptions::default()
},
)?;
let mut assembly = Assembly::new(control, "out.bin")?;
let held = assembly.submit_file(Path::new("image.bin.old"))?;
let (done, total) = assembly.progress();
println!("{held} blocks matched, {done} of {total} bytes held");
let stats = assembly.fetch()?;
println!(
"fetched {} bytes in {} requests",
stats.downloaded, stats.requests
);
assembly.finish()?;
Ok(())
}submit_file may be called several times. Every block any of the supplied
files holds is kept, and they are also the bases a sidecar patch is applied to.
Finishing
Assembly::finish verifies the assembled file against the SHA-1 the control
file records, and only then puts it in place. It returns Error::Incomplete
if any block is still missing and Error::DigestMismatch if the result does
not hash to what was promised. Dropping an assembly without finishing removes
its partial output, so a failed transfer never leaves a file behind that looks
like a result.
Watching progress
fn report(assembly: &mut zync::Assembly) {
assembly.set_progress(|done, total| {
println!("\r{done}/{total} blocks");
});
}A long transfer can also be cancelled, which returns Error::Aborted at the
next block boundary rather than running to completion:
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
fn cancellable(assembly: &mut zync::Assembly) -> Arc<AtomicBool> {
let flag = Arc::new(AtomicBool::new(false));
assembly.set_abort_flag(Arc::clone(&flag));
flag
}
// Raise it from another thread, or from a signal handler:
fn request_stop(flag: &AtomicBool) {
flag.store(true, Ordering::Relaxed);
}Supplying your own transport
Every remote read goes through the Transport trait, so the whole transfer can
be driven without a socket — which is how this crate's own tests work. An
embedder that must refuse a downgrade to plain HTTP, or that pins its own trust
roots, supplies an agent:
use std::sync::Arc;
use zync::{HttpTransport, Transport};
fn strict(assembly: zync::Assembly) -> zync::Assembly {
let http = HttpTransport::with_agent(
ureq::Agent::config_builder()
.https_only(true)
.build()
.new_agent(),
);
assembly.with_transport(Arc::new(http))
}See the sidecar guide for rebuilding missing regions from patches instead of fetching them.
2026 © All Rights Reserved.
- All the content is available under CC BY-SA 4.0, expect where otherwise stated.
- Source code is available on GitLab, licensed under Apache 2.0.
Last updated on
