Skip to content

calternal-sync

Local sync decisions are based on a durable three-way journal. A client records a new baseline only after it verifies a completed transfer. The planner has no filesystem side effects, so a crash before that commit retries work against the old baseline instead of forgetting bytes. Remote write retries stop when the server cannot identify an operation (#814). Agent callers can retain HTTP error fields through an opt-in client hook; default daemon error variants keep their existing recovery semantics (#835).

Source: crates/calternal-sync/src/lib.rs

pub struct Change

No doc comment.

Fields

  • pub cursor: i64
  • pub operation: String
  • pub path: String
  • pub old_path: Option<String>
  • pub item_id: String
  • pub content_hash: Option<String>
  • pub size: i64
  • pub mtime: i64

Implements: Clone, Debug, Deserialize, Eq, PartialEq

Source: crates/calternal-sync/src/remote.rs:161

pub struct ChangePage

No doc comment.

Fields

  • pub entries: Vec<Change>
  • pub cursor: i64

Implements: Clone, Debug, Deserialize, Eq, PartialEq

Source: crates/calternal-sync/src/remote.rs:173

pub struct ConflictRecord

No doc comment.

Fields

  • pub path: String
  • pub original_path: String
  • pub created_at: i64

Implements: Clone, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/journal.rs:41

pub struct InventoryEntry

No doc comment.

Fields

  • pub path: String
  • pub item_id: Option<String>
  • pub hash: String
  • pub size: u64
  • pub mtime: i64
  • pub file_id: Option<String>

Implements: Clone, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/plan.rs:12

pub struct Journal

No doc comment.

Implements: Clone

pub async fn open(path: &Path) -> Result<Self, Error>

No doc comment.

pub async fn entries(&self) -> Result<Vec<JournalEntry>, Error>

No doc comment.

pub async fn remote_entries(&self) -> Result<Vec<RemoteFile>, Error>

Read the last durable remote inventory. It is separate from entries: a path can be deferred locally while its remote change is still acknowledged in the feed.

pub async fn commit_remote_snapshot(
&self,
upserts: &[RemoteFile],
removed_paths: &[String],
cursor: i64,
scope: &str,
replace: bool,
full_scan_at: Option<i64>,
) -> Result<(), Error>

Persist feed changes with the remote cursor. A full inventory replaces the table and may rebase the cursor after feed expiry or server restore.

pub async fn commit_verified_batch(
&self,
entries: &[JournalEntry],
removed_paths: &[String],
cursor: i64,
) -> Result<(), Error>

Commit a verified baseline and its legacy cursor as a unit. The sync engine stores the remote feed cursor with remote_entries instead.

pub async fn commit_verified_sync_batch(
&self,
entries: &[JournalEntry],
removed_paths: &[String],
cursor: i64,
tag_updates: &[(String, BTreeSet<String>)],
removed_tag_paths: &[String],
finder_tags_initialized: bool,
) -> Result<(), Error>

Commit file revisions, Finder tag baselines, and the legacy cursor in one transaction after all local and server writes have succeeded.

pub async fn cursor(&self) -> Result<i64, Error>

No doc comment.

pub async fn remote_cursor(&self) -> Result<i64, Error>

The remote inventory cursor is separate from the verified sync baseline cursor in old journals. Fall back to the old key on upgrade.

pub async fn remote_scope(&self) -> Result<Option<String>, Error>

No doc comment.

pub async fn local_root_identity(&self) -> Result<Option<String>, Error>

The identity of the local sync root. The client stores this outside the root and checks it before it plans any destructive action.

pub async fn set_local_root_identity(&self, identity: &str) -> Result<(), Error>

Persist a verified local root identity before the engine may apply feed deltas or propagate local deletions.

pub async fn remote_full_scan_at(&self) -> Result<Option<i64>, Error>

The last full remote inventory committed with its cursor.

pub async fn finder_tag_baselines(&self) -> Result<BTreeMap<String, BTreeSet<String>>, Error>

Finder tag baselines are separate from file hashes because xattrs do not alter file bytes and must survive daemon restarts.

pub async fn finder_tags_initialized(&self) -> Result<bool, Error>

No doc comment.

pub async fn save_pending(&self, pending: &PendingUpload) -> Result<(), Error>

No doc comment.

pub async fn pending(&self) -> Result<Vec<PendingUpload>, Error>

No doc comment.

pub async fn record_download_temp(&self, relative: &str) -> Result<(), Error>

Record the exact temp pathname before opening it. Recovery can remove only this daemon’s partial download, without guessing from a prefix.

pub async fn clear_download_temp(&self, relative: &str) -> Result<(), Error>

No doc comment.

pub async fn recover_download_temps(&self, root: &Path) -> Result<(), Error>

Startup removes only paths recorded before a download began. A completed rename has no temp left, so its stale record is harmless.

pub async fn record_conflict(&self, conflict: &ConflictRecord) -> Result<(), Error>

No doc comment.

pub async fn conflicts(&self) -> Result<Vec<ConflictRecord>, Error>

No doc comment.

pub async fn read_conflicts(path: &Path) -> Result<Vec<ConflictRecord>, Error>

The CLI can inspect conflicts while the daemon owns the pair’s writer lock. SQLite readers see a committed snapshot in WAL mode.

Source: crates/calternal-sync/src/journal.rs:48

pub struct JournalEntry

No doc comment.

Fields

  • pub path: String
  • pub item_id: Option<String>
  • pub hash: String
  • pub size: u64
  • pub mtime: i64
  • pub file_id: Option<String>

Implements: Clone, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/journal.rs:22

pub struct LocalWatcher

No doc comment.

pub fn new(root: &Path) -> Result<Self, Error>

No doc comment.

pub async fn next_change(&mut self) -> Result<(), Error>

Return after one event. Debounce belongs to the caller after this future completes: a cancelled select arm must never consume a wake-up.

Read-only access events do not count: a reconcile pass opens and hashes every file, and treating those opens as changes made the daemon rescan in a loop forever (several % CPU while idle). A watcher error (for example a queue overflow) is a wake-up, not a reason to stop the pair; the scan finds what the queue lost.

Source: crates/calternal-sync/src/local.rs:1257

pub struct PendingUpload

No doc comment.

Fields

  • pub path_at_detection: String
  • pub file_id: String
  • pub hash_at_detection: String
  • pub upload_id: Option<String>
  • pub upload_offset: u64

Implements: Clone, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/journal.rs:32

pub struct RemoteClient

No doc comment.

Implements: Clone

pub fn new(server_url: &str, token: String) -> Result<Self, Error>

No doc comment.

pub fn new_with_policy(
server_url: &str,
token: String,
timeout: Duration,
retries: u8,
) -> Result<Self, Error>

Build a CLI client with a per-request timeout and bounded retry count. The sync daemon keeps using Self::new and therefore keeps its existing request timing and retry behavior.

pub fn with_http_details(mut self) -> Self

Preserve server error fields for an agent caller without changing the daemon’s cursor, precondition and status handling (#835, DESIGN §41).

pub async fn changes(&self, cursor: i64, limit: u16) -> Result<ChangePage, Error>

No doc comment.

pub async fn head_cursor(&self) -> Result<i64, Error>

No doc comment.

pub async fn list_tree(&self, remote_folder: &str) -> Result<Vec<RemoteFile>, Error>

List the remote snapshot used for a first baseline, an expired cursor, or the periodic consistency check. Folders are listed LIST_CONCURRENCY at a time. One request per folder, one after the other, made the listing of a tree with 100 folders the largest part of every pass (#46); the server answers independent folder listings in parallel.

pub async fn download(&self, path: &str) -> Result<Response, Error>

No doc comment.

pub async fn stat_file(&self, path: &str) -> Result<RemoteFile, Error>

No doc comment.

pub async fn mkdir(&self, path: &str) -> Result<(), Error>

No doc comment.

pub async fn is_directory(&self, path: &str) -> Result<bool, Error>

Is path a folder? Folders have no content hash, so moving or trashing one cannot carry an If-Match precondition.

pub async fn move_if_hash(
&self,
source: &str,
destination: &str,
hash: &str,
) -> Result<(), Error>

No doc comment.

pub async fn move_path(
&self,
source: &str,
destination: &str,
hash: Option<&str>,
) -> Result<(), Error>

Move or rename a file or folder. hash guards a file move against a concurrent edit; a folder move passes None.

pub async fn wakeups(&self) -> Result<Response, Error>

No doc comment.

pub async fn create_upload(
&self,
path: &str,
length: u64,
expected_hash: Option<&str>,
) -> Result<String, Error>

No doc comment.

pub async fn create_upload_with_mtime(
&self,
path: &str,
length: u64,
expected_hash: Option<&str>,
source_mtime_ns: Option<i64>,
) -> Result<String, Error>

No doc comment.

RemoteClient::create_upload_with_mtime_and_path

Section titled “RemoteClient::create_upload_with_mtime_and_path”
pub async fn create_upload_with_mtime_and_path(
&self,
path: &str,
length: u64,
expected_hash: Option<&str>,
source_mtime_ns: Option<i64>,
) -> Result<(String, Option<String>), Error>

Create a TUS upload and return the installed path when the server completed a zero-length upload during creation.

pub async fn upload_offset(&self, id: &str) -> Result<u64, Error>

No doc comment.

pub async fn upload_chunk(&self, id: &str, offset: u64, bytes: Vec<u8>) -> Result<u64, Error>

No doc comment.

pub async fn upload_chunk_with_path(
&self,
id: &str,
offset: u64,
bytes: Vec<u8>,
) -> Result<(u64, Option<String>), Error>

Upload one TUS chunk and return the installed path when this chunk completed the upload.

pub async fn terminate_upload(&self, id: &str) -> Result<(), Error>

No doc comment.

pub async fn trash_if_hash(&self, path: &str, hash: &str) -> Result<(), Error>

No doc comment.

pub async fn trash_path(&self, path: &str, hash: Option<&str>) -> Result<(), Error>

Trash a file or folder. hash guards a file against a concurrent edit; a folder passes None.

Source: crates/calternal-sync/src/remote.rs:23

pub struct RemoteFile

No doc comment.

Fields

  • pub path: String
  • pub item_id: String
  • pub hash: String
  • pub size: u64
  • pub mtime: i64

Implements: Clone, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/remote.rs:179

pub struct SyncConfig

No doc comment.

Fields

  • pub device_name: String
  • pub pairs: Vec<SyncPair>

Implements: Clone, Debug, Default, Deserialize, Serialize

pub fn load() -> Result<Self, Error>

Load and validate private sync.json (#920; DESIGN §24). A missing file gives no pairs and uses the host name, or installation, as the Installation label. Invalid JSON, permissions, or pair settings return an error; this does not overwrite the existing file.

pub fn save(&self) -> Result<(), Error>

No doc comment.

pub fn add(
&mut self,
server_url: String,
local_folder: &Path,
remote_folder: String,
include: Vec<String>,
) -> Result<&SyncPair, Error>

No doc comment.

pub fn remove(&mut self, id: &str) -> bool

No doc comment.

Source: crates/calternal-sync/src/config.rs:133

pub struct SyncEngine

No doc comment.

pub async fn open(pair: SyncPair, device_name: String) -> Result<Self, Error>

No doc comment.

pub async fn with_token(
pair: SyncPair,
device_name: String,
token: String,
) -> Result<Self, Error>

No doc comment.

pub fn pair(&self) -> &SyncPair

No doc comment.

pub fn deferred_paths(&self) -> usize

How many paths the last committed pass deferred (see defers_download).

pub async fn wakeups(&self) -> Result<reqwest::Response, Error>

No doc comment.

pub async fn reconcile_once(&self) -> Result<usize, Error>

Run one three-way sync pass with the mass-deletion guard enabled (#920). Return the number of changed actions, including platform Tag edits. Use the confirmed variant only after explicit User confirmation (DESIGN §24).

pub async fn reconcile_once_confirmed(&self, allow_mass_delete: bool) -> Result<usize, Error>

Reconcile baseline, local files, and remote revisions for one pass (#920). allow_mass_delete bypasses the deletion guard only for this call; it does not persist consent. Preserve conflicts and defer unstable paths. Commit verified revisions, Tag baselines, and the remote cursor together after the pass writes, then return its change count (DESIGN §24).

Source: crates/calternal-sync/src/engine.rs:31

pub struct SyncPair

No doc comment.

Fields

  • pub id: String
  • pub server_url: String
  • pub local_folder: PathBuf
  • pub remote_folder: String
  • pub include: Vec<String>
  • pub paused: bool
  • pub guarded: bool
  • pub guard_reason: Option<SyncGuardReason>: The reason for a safety pause, exposed by calternal sync status.
  • pub allow_mass_delete_once: bool: One user-confirmed pass may exceed the deletion threshold. The daemon clears this only after that pass commits its verified journal state.

Implements: Clone, Debug, Deserialize, Serialize

pub fn selected(&self, remote_path: &str) -> bool

No doc comment.

pub fn relative_path<'a>(&self, remote_path: &'a str) -> Option<&'a str>

No doc comment.

pub fn remote_path(&self, relative: &str) -> Result<String, Error>

No doc comment.

pub fn journal_path(&self) -> Result<PathBuf, Error>

No doc comment.

Source: crates/calternal-sync/src/config.rs:25

pub enum Action

No doc comment.

Variants

  • Synced
  • Upload
  • Download
  • Conflict
  • TrashLocal
  • TrashRemote
  • RemovedOnBoth

Implements: Clone, Copy, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/plan.rs:28

pub enum Error

Errors from local journal operations. A daemon stops this pair on an error instead of treating a missing or corrupt journal as an empty baseline.

Variants

  • Journal(#[from] sqlx::Error)
  • InvalidPath
  • PendingIdentity
  • ConcurrentChange
  • UnsupportedPath
  • Io(#[from] std::io::Error)
  • Lock(#[from] std::fs::TryLockError)
  • Watch(#[from] notify::Error)
  • Http(#[from] reqwest::Error)
  • UnknownWriteResult
  • InvalidRemote
  • Server(u16)
  • ServerDetails(Box<calternal_api::HttpFailure>): CLI callers opt in to complete HTTP fields; daemon status variants stay stable (#835).
  • CursorExpired
  • UploadExpired: The server no longer knows this tus upload: it expired after the idle timeout or its lifetime, or a failed finalization released it. The client starts a new upload from byte 0.
  • HashChanged
  • RemoteListingTorn
  • RemotePreconditionFailed
  • LocalConflict
  • LocalDestinationChanged
  • Credential
  • MassDeletion
  • LocalRootChanged
  • InvalidFinderTags

Implements: Debug, Error, From<SendError>

Source: crates/calternal-sync/src/lib.rs:37

pub enum SendError

Distinguish a failed read from a write that may already have committed.

Variants

  • Request(#[source] reqwest::Error)
  • UnknownWriteResult

Implements: Debug, Error

Source: crates/calternal-sync/src/remote.rs:102

pub enum Side

No doc comment.

Variants

  • Local
  • Remote

Implements: Clone, Copy, Debug, Eq, PartialEq

Source: crates/calternal-sync/src/plan.rs:22

pub enum SyncGuardReason

Why the daemon paused a sync pair for user confirmation.

Variants

  • MassDeletion
  • LocalRootChanged

Implements: Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize

Source: crates/calternal-sync/src/config.rs:19

pub fn classify(
baseline: Option<&JournalEntry>,
local: Option<&InventoryEntry>,
remote: Option<&InventoryEntry>,
) -> Action

No doc comment.

Source: crates/calternal-sync/src/plan.rs:38

pub fn conflict_name<Tz: TimeZone>(path: &str, device: &str, time: DateTime<Tz>) -> String
where
Tz::Offset: std::fmt::Display,

Name the losing side of a conflict. The time is formatted in the zone of time; the daemon passes local time so the name matches the clock the user saw when they edited (a UTC stamp read two hours off in Berlin).

Source: crates/calternal-sync/src/plan.rs:222

pub fn deletion_guard(deletions: usize, baseline_files: usize) -> bool

Pause before a single pass can propagate a large deletion. The relative threshold applies to the last verified pair size, never the current scan.

The 30 % rule needs more than GUARD_MIN_RELATIVE deletions: without the floor, deleting one file of a three-file folder paused the pair, so a new user’s first ordinary delete stopped sync. Losing every file of the pair (an unmounted disk, rm -rf) still pauses at any size.

Source: crates/calternal-sync/src/plan.rs:169

pub fn device_name() -> Option<String>

The machine’s host name, used as the installation name at login and as <device> in conflict copy names. $HOSTNAME is a shell variable that is usually not exported (never under systemd), so reading it named every device “installation”; ask the kernel instead.

Source: crates/calternal-sync/src/config.rs:119

pub fn follow_pending<'a>(
pending: &PendingUpload,
current: &'a [InventoryEntry],
) -> Option<&'a InventoryEntry>

Follow an inode through any number of renames. A hash-only fallback is accepted only when unique, so a duplicate file cannot steal a pending upload. Re-read and hash the returned path before sending bytes.

Source: crates/calternal-sync/src/plan.rs:136

pub fn forget_server(server_url: &str) -> Result<(), Error>

Forget the remembered server after logout from that server.

Source: crates/calternal-sync/src/credentials.rs:171

pub async fn install_download(
root: &Path,
relative: &str,
expected_remote_hash: &str,
expected_local_hash: Option<&str>,
response: reqwest::Response,
) -> Result<(), Error>

Download into a new file beside the destination, verify BLAKE3, fsync, then publish by a create-if-absent atomic rename. An existing destination first moves to Trash. A concurrent new destination makes publication fail without replacing it. The daemon journals its temp pathname before opening it; recovery removes a partial temp after a crash. A standalone CLI transfer leaves an interrupted temp under its reserved name, never at the target.

The daemon uses install_download_tracked, which also returns the local identity of the installed file.

Source: crates/calternal-sync/src/local.rs:720

pub fn load_token(server_url: &str) -> Result<String, Error>

Read the credential for one server. Agent containers may use a pre-issued data-only CALTERNAL_TOKEN only when CALTERNAL_TOKEN_SERVER binds it to this server, so a different --server cannot receive the bearer.

Source: crates/calternal-sync/src/credentials.rs:104

pub fn possible_rename(old_path: &str, new_path: &str) -> bool

Can a local file seen at new_path be a rename of the baseline file at old_path? A file cannot move to a path inside itself: when the new path is under the old one, the old file was replaced by a folder (a type flip), and any shared identity is a reused inode, not a rename.

Source: crates/calternal-sync/src/plan.rs:126

pub async fn read_http_failure(mut response: Response) -> calternal_api::HttpFailure

Share one bounded failure reader between CLI actions and transfers (#835). The decoder preserves server fields and delay hints; it never replays a write.

Source: crates/calternal-sync/src/remote.rs:113

pub fn remember_server(server_url: &str) -> Result<(), Error>

Record the server of the last successful login. Commands without --server use it, so calternal login <url> followed by calternal whoami works before any sync pair exists. It holds no secret.

Source: crates/calternal-sync/src/credentials.rs:155

pub fn remembered_server() -> Result<Option<String>, Error>

The server of the last successful login, if it is still logged in.

Source: crates/calternal-sync/src/credentials.rs:162

pub fn remove_token(server_url: &str) -> Result<(), Error>

No doc comment.

Source: crates/calternal-sync/src/credentials.rs:136

pub fn same_file_identity(left: &str, right: &str) -> bool

Do two local file ids (dev:ino[:birth], see local_file_id) name the same file? Device and inode must match. The birth time must also match when both ids have one; an id without one comes from a file system without birth times or from a journal written before ids carried them, and then only dev:ino can be compared.

Source: crates/calternal-sync/src/plan.rs:92

pub fn save_token(server_url: &str, token: &str) -> Result<(), Error>

No doc comment.

Source: crates/calternal-sync/src/credentials.rs:121

pub fn scan_local(root: &Path) -> Result<Vec<InventoryEntry>, Error>

No doc comment.

Source: crates/calternal-sync/src/local.rs:930

pub async fn send_with_retry(
client: &Client,
request: RequestBuilder,
retries: u8,
) -> Result<Response, SendError>

Send one HTTP request with bounded retries for safe reads only (#814, #835). A transient gateway status can follow a committed write. Until a route has a server deduplication contract, neither status nor a caller-supplied header makes replay safe, so a write reports an unknown result instead.

Source: crates/calternal-sync/src/remote.rs:35

pub fn trash_local(path: &Path) -> Result<(), Error>

Move a remote-deleted local file to the operating system Trash. A cross filesystem rename fails closed; the daemon must pause that pair instead of falling back to unlink or an unverified copy.

Source: crates/calternal-sync/src/local.rs:493