Help us shape the future of Go! Take the 2026 Go Developer Survey

store

package
v0.18.1 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package store manages a volume's local on-disk state: a content-addressed blob store, the per-device journals, and small JSON state files. Everything a volume needs works offline; the remote is only used to exchange blobs and journals.

Layout under <beardrive home>/volumes/<volume>/:

blobs/<aa>/<sha256>   content-addressed file contents (immutable)
journal/<device>.jsonl per-device op logs (own + cached copies of peers)
state.json            what is currently materialized in the folder
sync.json             lamport clock + push cursor
lock                  flock guarding cycles

Index

Constants

View Source
const (
	AccessOK       = ""          // normal read+write sync
	AccessReadOnly = "read-only" // pushes refused: pull-only
	AccessNone     = "no-access" // pulls refused too: sync paused
)

Access records how the hub answered this device on the last cycle that reached it. It is persisted so `bdrive status` — which never runs a cycle — can report a degraded state, and so the daemon can log a transition once instead of on every tick.

Variables

This section is empty.

Functions

func Paused added in v0.10.0

func Paused(dir string) bool

Paused reports whether syncing is paused for the volume dir.

func PausedMount added in v0.18.0

func PausedMount(dir, mountID string) bool

PausedMount reports whether syncing is paused for the volume or mount.

func SetPaused added in v0.10.0

func SetPaused(dir string, on bool) error

SetPaused sets or clears the paused marker.

func SetPausedMount added in v0.18.0

func SetPausedMount(dir, mountID string, on bool) error

SetPausedMount sets or clears the mount-specific pause marker.

func UnderRoot added in v0.15.0

func UnderRoot(root, p string) bool

UnderRoot reports whether p resolves inside root ON DISK — following every symlink on the way, including one planted in a directory that already exists. p need not exist: the deepest existing ancestor is what gets resolved, so the answer is available before anything is created.

A lexical check (filepath.Rel, path.Clean, HasPrefix) answers a question about the STRING. os.MkdirAll, os.CreateTemp, os.Rename and os.Open all answer the question about the FILESYSTEM, and that is the one that decides where the bytes land: "docs/x.md" is a clean relative path by every lexical test and lands outside the root the moment "docs" is a symlink. It lives in this package because the two callers that need it — the syncer materializing a peer's op into a mount, and the file:// backend resolving a key under the hub's storage root — are the same question about different roots.

func WriteFileAtomic

func WriteFileAtomic(path string, data []byte, mode os.FileMode) error

WriteFileAtomic writes data via a temp file in the same directory + rename.

func WriteJSONAtomic

func WriteJSONAtomic(path string, v any) error

WriteJSONAtomic writes v as JSON via temp-file + rename. 0600 for the same reason as the journals: these files list a private project's paths and the accounts that touched them, inside a 0755 $BDRIVE_HOME.

Types

type CachedFile

type CachedFile struct {
	Blob    string `json:"blob"`
	Size    int64  `json:"size"`
	Mode    uint32 `json:"mode"`
	MTimeNS int64  `json:"mtime_ns"`
	// Link is the target of a symbolic link. When set, Blob is empty: the
	// cached entry describes a link, not content, and change detection also
	// compares this so retargeting a link to a same-length path is still seen.
	Link string `json:"link,omitempty"`
}

CachedFile records what beardrive last wrote to / observed in the working folder for a path. Size+MTimeNS make change detection cheap; Blob ties it back to content. The cache is per mount (one volume can be materialized into several folders, each with its own stat fingerprints).

type InboundEvent added in v0.15.0

type InboundEvent struct {
	Path    string    `json:"path"`
	Deleted bool      `json:"deleted,omitempty"`
	Time    time.Time `json:"time"`
}

InboundEvent is one path a cycle wrote or removed on a peer's behalf (mount-relative).

type Note added in v0.4.0

type Note struct {
	Text    string    `json:"text"`
	Expires time.Time `json:"expires,omitzero"` // zero = never expires
}

Note is the on-disk shape of the session note.

type ReadEvent added in v0.4.0

type ReadEvent struct {
	Path string `json:"path"`
	// Session is the agent session the read happened in, from the same hook
	// payload the sync hook stamps journal.Op.Session from. Empty for reads
	// with no session (a platform that reports none, or an older client).
	Session string    `json:"session,omitempty"`
	Time    time.Time `json:"time"`
}

ReadEvent is one observed read of a synced file (mount-relative path).

type Store

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

func Open

func Open(dir string) (*Store, error)

func (*Store) AllOps

func (s *Store) AllOps() ([]journal.Op, error)

AllOps returns the union of every journal known locally.

func (*Store) AppendOps

func (s *Store) AppendOps(device string, ops []journal.Op) error

func (*Store) BlobPath

func (s *Store) BlobPath(sum string) string

BlobPath is the on-disk location of a blob. A key that is not a sha256 resolves to a name inside the blob dir that PutBlobReader never writes, so HasBlob and OpenBlob fail naturally instead of reaching somewhere else.

func (*Store) ClearNote added in v0.4.0

func (s *Store) ClearNote() error

ClearNote removes the session note.

func (*Store) ClearNoteForMount added in v0.18.0

func (s *Store) ClearNoteForMount(mountID string) error

ClearNoteForMount removes a mount's note and its primary-mount legacy copy.

func (*Store) ClearPendingReads added in v0.4.0

func (s *Store) ClearPendingReads() error

ClearPendingReads drops the batch PendingReads returned, after a successful report.

func (*Store) DeviceOps

func (s *Store) DeviceOps(device string) ([]journal.Op, error)

func (*Store) Devices

func (s *Store) Devices() ([]string, error)

Devices lists device IDs that have a local journal copy.

func (*Store) Dir

func (s *Store) Dir() string

func (*Store) DrainInbound added in v0.15.0

func (s *Store) DrainInbound() ([]InboundEvent, error)

DrainInbound returns the queued batch from the legacy spool.

func (*Store) DrainInboundForMount added in v0.18.0

func (s *Store) DrainInboundForMount(mountID string) ([]InboundEvent, error)

DrainInboundForMount drains a mount-specific spool. Legacy spool events are also drained when present so older callers remain visible to the hook.

func (*Store) HasBlob

func (s *Store) HasBlob(sum string) bool

func (*Store) HasCache added in v0.18.0

func (s *Store) HasCache(mountID string) bool

HasCache reports whether a materialization cache file already exists on disk for mountID. A mount with no cache file has never completed a sync cycle in its working folder, even when it attaches to a volume store another worktree already populated.

func (*Store) JournalPath

func (s *Store) JournalPath(device string) string

func (*Store) LoadCache

func (s *Store) LoadCache(mountID string) (map[string]CachedFile, error)

func (*Store) LoadNote added in v0.4.0

func (s *Store) LoadNote() string

LoadNote returns the live session note, or "" if none is set or it has expired. Never errors: a missing or unreadable note is just no note.

func (*Store) LoadNoteForMount added in v0.18.0

func (s *Store) LoadNoteForMount(mountID string) string

LoadNoteForMount returns a live mount note. Only the primary mount may fall back to a legacy note, and only when its mount-specific file is absent.

func (*Store) LoadSecrets added in v0.16.0

func (s *Store) LoadSecrets(mountID string) (map[string][]secrets.Finding, error)

LoadSecrets returns the findings recorded for each mount-relative path.

func (*Store) LoadSync

func (s *Store) LoadSync() (SyncState, error)

func (*Store) Lock

func (s *Store) Lock() (func() error, error)

Lock takes an exclusive flock for the volume, serializing sync cycles between the daemon and one-shot commands. Blocks until acquired.

func (*Store) LogInbound added in v0.15.0

func (s *Store) LogInbound(rel string, deleted bool) error

LogInbound appends one materialized path to the legacy spool for backward compatibility with callers and installations that do not have a mount id.

func (*Store) LogInboundForMount added in v0.18.0

func (s *Store) LogInboundForMount(mountID, rel string, deleted bool) error

LogInboundForMount appends one materialized path to the mount's spool.

func (*Store) LogRead added in v0.4.0

func (s *Store) LogRead(rel, session string) error

LogRead appends one read event to the spool. Single-line O_APPEND writes keep concurrent hook invocations from interleaving.

func (*Store) OpenBlob

func (s *Store) OpenBlob(sum string) (*os.File, error)

func (*Store) PendingReads added in v0.4.0

func (s *Store) PendingReads() ([]ReadEvent, error)

PendingReads returns the queued batch awaiting report, deduplicated by (path, session) — latest time wins. Not by path alone: two agent sessions on one device between syncs both reading wiki/a.md are two reads by two sessions, and collapsing them would report one, carrying whichever session happened to flush last — one session's reads silently credited to another. The spool is rotated aside first, so events logged after this call land in a fresh spool; the batch survives until ClearPendingReads — a failed report is simply retried next cycle.

func (*Store) PutBlobBytes

func (s *Store) PutBlobBytes(b []byte) (string, int64, error)

func (*Store) PutBlobFile

func (s *Store) PutBlobFile(path string) (string, int64, error)

func (*Store) PutBlobReader

func (s *Store) PutBlobReader(r io.Reader) (string, int64, error)

PutBlobReader streams r into the blob store, returning its sha256 and size.

func (*Store) SaveCache

func (s *Store) SaveCache(mountID string, c map[string]CachedFile) error

func (*Store) SaveNote added in v0.4.0

func (s *Store) SaveNote(text string, ttl time.Duration) error

SaveNote sets the session note. ttl > 0 bounds its life; empty text clears.

func (*Store) SaveNoteForMount added in v0.18.0

func (s *Store) SaveNoteForMount(mountID, text string, ttl time.Duration) error

SaveNoteForMount sets a mount's session note without labeling sibling edits. The primary mount also keeps the legacy note visible to older callers.

func (*Store) SaveSecrets added in v0.16.0

func (s *Store) SaveSecrets(mountID string, f map[string][]secrets.Finding) error

func (*Store) SaveSync

func (s *Store) SaveSync(st SyncState) error

type SyncState

type SyncState struct {
	Remote    string `json:"remote,omitempty"`
	Lamport   int64  `json:"lamport"`
	PushedOps int64  `json:"pushed_ops"`       // how many of our own ops the remote has
	Access    string `json:"access,omitempty"` // "", "read-only", or "no-access"
	// AccessReason is the hub's own words for the last refusal. Without it every
	// 403 renders as the same "read-only (pull only)" line, and the hub's most
	// actionable answer — "this device is not registered to your account on this
	// hub; run `bdrive login`" — reached nobody: the one state a user cannot
	// diagnose from the outside was the one the CLI summarized away.
	AccessReason string `json:"access_reason,omitempty"`

	// IgnoreAccepted is the .bdriveignore text whose scan scope THIS device has
	// accepted, and IgnorePulled is the text a peer's version last wrote here.
	// Together they tell a locally-authored rule change from one that arrived
	// over the wire, which is what keeps a teammate's `!` from widening what
	// leaves this disk. See syncer.Filter.SkipUp.
	IgnoreAccepted string `json:"ignore_accepted,omitempty"`
	IgnorePulled   string `json:"ignore_pulled,omitempty"`

	// ReadOnly are the folder prefixes this account may sync down but must
	// never journal a change to, as the hub last reported them
	// (remote.Scoper). Persisted so an offline cycle keeps honouring the last
	// answer instead of falling back to "everything is writable" and building
	// up a journal the hub will refuse the moment the network returns.
	ReadOnly []string `json:"read_only,omitempty"`
	// Denied are prefixes this account may not read at all. Persisted for the
	// same reason ReadOnly is: an unreachable hub must not widen what this
	// device is willing to journal.
	Denied []string `json:"denied,omitempty"`
	// ScopeTag identifies the answer ReadOnly came from. It changes only when
	// what THIS account can see changes.
	ScopeTag string `json:"scope_tag,omitempty"`
}

Jump to

Keyboard shortcuts

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