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
- func Paused(dir string) bool
- func SetPaused(dir string, on bool) error
- func UnderRoot(root, p string) bool
- func WriteFileAtomic(path string, data []byte, mode os.FileMode) error
- func WriteJSONAtomic(path string, v any) error
- type CachedFile
- type InboundEvent
- type Note
- type ReadEvent
- type Store
- func (s *Store) AllOps() ([]journal.Op, error)
- func (s *Store) AppendOps(device string, ops []journal.Op) error
- func (s *Store) BlobPath(sum string) string
- func (s *Store) ClearNote() error
- func (s *Store) ClearPendingReads() error
- func (s *Store) DeviceOps(device string) ([]journal.Op, error)
- func (s *Store) Devices() ([]string, error)
- func (s *Store) Dir() string
- func (s *Store) DrainInbound() ([]InboundEvent, error)
- func (s *Store) HasBlob(sum string) bool
- func (s *Store) JournalPath(device string) string
- func (s *Store) LoadCache(mountID string) (map[string]CachedFile, error)
- func (s *Store) LoadNote() string
- func (s *Store) LoadSync() (SyncState, error)
- func (s *Store) Lock() (func() error, error)
- func (s *Store) LogInbound(rel string, deleted bool) error
- func (s *Store) LogRead(rel, session string) error
- func (s *Store) OpenBlob(sum string) (*os.File, error)
- func (s *Store) PendingReads() ([]ReadEvent, error)
- func (s *Store) PutBlobBytes(b []byte) (string, int64, error)
- func (s *Store) PutBlobFile(path string) (string, int64, error)
- func (s *Store) PutBlobReader(r io.Reader) (string, int64, error)
- func (s *Store) SaveCache(mountID string, c map[string]CachedFile) error
- func (s *Store) SaveNote(text string, ttl time.Duration) error
- func (s *Store) SaveSync(st SyncState) error
- type SyncState
Constants ¶
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 UnderRoot ¶ added in v0.15.0
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 ¶
WriteFileAtomic writes data via a temp file in the same directory + rename.
func WriteJSONAtomic ¶
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"`
}
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 (*Store) BlobPath ¶
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) ClearPendingReads ¶ added in v0.4.0
ClearPendingReads drops the batch PendingReads returned, after a successful report.
func (*Store) DrainInbound ¶ added in v0.15.0
func (s *Store) DrainInbound() ([]InboundEvent, error)
DrainInbound returns the queued batch and clears it, deduplicated by path (latest event wins, so a path written and then deleted reports as deleted). The spool is rotated aside first, so events logged during the drain land in a fresh spool — the drain runs outside the volume flock, and a daemon on the same mount may be appending.
One call, unlike PendingReads/ClearPendingReads: those are two steps because a read report can fail over the network and must be retried, and rendering a string onto stdout cannot.
func (*Store) JournalPath ¶
func (*Store) LoadNote ¶ added in v0.4.0
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) Lock ¶
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
LogInbound appends one materialized path to the spool. Single-line O_APPEND writes keep the daemon and a concurrent CLI cycle from interleaving.
func (*Store) LogRead ¶ added in v0.4.0
LogRead appends one read event to the spool. Single-line O_APPEND writes keep concurrent hook invocations from interleaving.
func (*Store) PendingReads ¶ added in v0.4.0
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) PutBlobReader ¶
PutBlobReader streams r into the blob store, returning its sha256 and size.
type SyncState ¶
type SyncState struct {
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"`
}