syncer

package
v0.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

Documentation

Overview

Package syncer drives a volume's sync cycle:

scan → commit local ops → pull peer journals → preserve conflicts →
materialize merged state → push blobs + own journal

Scanning always happens before pulling, so local edits are committed to the journal (and their content captured in the blob store) before any remote state can overwrite the working folder. Concurrent edits resolve deterministically last-writer-wins; the losing local version is preserved as a "<name>.bdrive-conflict-<device>-<time>" file that syncs like any other.

Index

Constants

View Source
const IgnoreFile = ".bdriveignore"

IgnoreFile is the per-folder opt-out list at the mount root. It uses a gitignore-style syntax and, unlike the .bdrive settings file, syncs like any other file so every device shares the same rules.

Variables

This section is empty.

Functions

func LogEntries

func LogEntries(st *store.Store, pathFilter string, limit int) ([]journal.Op, error)

LogEntries returns the volume history, newest first.

func NotSyncedFiles added in v0.12.0

func NotSyncedFiles(notSynced []Entry) int

NotSyncedFiles is how many files the not-synced list stands for: collapsed directories count their whole subtree, nested mounts count zero because they do sync — through their own project.

Types

type Entry added in v0.12.0

type Entry struct {
	Path   string
	Files  int
	Nested bool // syncs through its own project — not excluded
}

Entry is one line of the not-synced list. Path ends in "/" when a whole directory collapsed to a single line; Files is how many files it holds.

func Explain added in v0.12.0

func Explain(folder string, include []string) (synced []string, notSynced []Entry, err error)

Explain reports what the sync cycle would and would not send for a folder. It is a pure read: no Session, no volume lock, no network, no writes — the answer comes from the same walk the cycle itself uses, so it cannot drift.

func (Entry) IsDir added in v0.12.0

func (e Entry) IsDir() bool

IsDir reports whether the entry stands for a whole directory rather than a single file.

type Filter

type Filter struct {
	// contains filtered or unexported fields
}

Filter decides which paths sync. A path syncs when it is not ignored and, if an include list is set, matches at least one include pattern.

Pattern syntax (a practical gitignore subset): one pattern per line, blank lines and #-comments skipped, `!` re-includes, a trailing `/` matches directories only, a `/` anywhere else anchors the pattern to the mount root (otherwise it matches at any depth), `*` matches within a path segment, `**` across segments, `?` a single character.

func LoadFilter added in v0.4.0

func LoadFilter(folder string, include []string) (*Filter, error)

LoadFilter builds the filter for a folder from its .bdriveignore (if any) plus the include list from the .bdrive settings file — the exact rules the sync cycle applies, for callers outside the cycle (e.g. `bdrive read-log` deciding whether an agent-read path is part of the project).

func (*Filter) Negated added in v0.12.0

func (f *Filter) Negated() bool

Negated reports whether any `!` rule is in play. Scope narrowing is written as negation rules, so this is how callers tell "these rules exclude a few things" from "these rules exclude everything but a few things" — the difference between a safe prune and a destructive one.

func (*Filter) PruneDir

func (f *Filter) PruneDir(rel string) bool

PruneDir reports whether a whole directory can be skipped during the scan walk. Pruning is conservative: never with `!` rules (a child could be re-included) or an include list (a deep child could match).

func (*Filter) Skip

func (f *Filter) Skip(rel string) bool

Skip reports whether a file path should not sync.

type Progress added in v0.3.1

type Progress struct {
	Done, Total    int
	Bytes, ToBytes int64
}

Progress reports upload progress during a cycle's push phase, so the CLI can draw a bar. Total/TotalBytes are set once when the push starts; Done/Bytes climb as blobs finish. Nil OnProgress means no reporting (the daemon).

type Result

type Result struct {
	LocalOps     int  // local changes committed to the journal
	PulledOps    int  // ops received from other devices
	Conflicts    int  // conflict copies created
	Pruned       int  // paths removed from the hub by --prune (kept on disk)
	Materialized int  // files written/removed in the working folder
	Pushed       bool // own journal/blobs uploaded
	Offline      bool // remote configured but unreachable this cycle
	OfflineErr   error
	ReadOnly     bool // the hub refused our push: pull-only from here
	NoAccess     bool // the hub refused our pull: sync paused, nothing touched
	AccessErr    error
}

Result summarizes one sync cycle.

Offline, ReadOnly, and NoAccess are three different answers and must not be conflated: offline means the hub could not be reached and everything should be retried; ReadOnly means it refused our push (we keep pulling, local ops stay journaled and unpushed); NoAccess means it refused our pull too, so the cycle does nothing at all and leaves the working folder alone. Regaining access self-heals on a later cycle with no manual step.

func (*Result) Activity

func (r *Result) Activity() bool

type Session

type Session struct {
	Folder  string
	MountID string // the stable project mount id from .bdrive/config.json
	Store   *store.Store
	Device  config.Device
	// Account is the signed-in user (from `bdrive login`); ops carry it so
	// history shows who changed what. Zero on offline/no-auth setups —
	// Device.Author remains the fallback identity.
	Account config.Settings
	// Note, when set, is stamped into every op this session commits — session
	// context like "claude-code session <id>". Empty means fall back to the
	// store's persisted session note (store.LoadNote), which lets a one-shot
	// `bdrive sync --note` leave context that the daemon's later scans also
	// stamp. Conflict-copy ops keep their own explanatory note.
	Note string
	// Prune makes this cycle reconcile the hub against the shared ignore
	// rules: every path the remote still holds that .bdriveignore (or a
	// builtin never-sync rule) now excludes is journaled as a delete, so it
	// leaves the hub while staying on disk on every device. Off by default —
	// plain `bdrive sync` and the daemon never set it, because pruning must
	// be a deliberate act, never a side effect of editing .bdriveignore.
	Prune   bool
	Backend remote.Backend // nil = work offline
	// OnProgress, when set, is called during push with upload progress. It may
	// be invoked concurrently from upload workers, so it must be safe to call
	// from multiple goroutines.
	OnProgress func(Progress)
}

Session ties a working folder to its volume store and (optionally) remote.

func (*Session) Cycle

func (s *Session) Cycle(ctx context.Context) (*Result, error)

Cycle runs one full scan/sync/materialize pass under the volume lock.

func (*Session) Restore added in v0.12.0

func (s *Session) Restore(ctx context.Context, path, sha string) error

Restore writes the historical version sha of path back into the working folder as an ordinary local edit. The next Cycle journals it like any other change — nothing here appends to a journal, and no journal is ever rewritten. Restoring is exactly the edit a human could have made by hand, which is why the sync engine needs no new write path for it.

It does not take the volume flock: Cycle does, and holding it here would deadlock the caller that runs both.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL