Documentation
¶
Overview ¶
Package versioning persists historical snapshots of node contents inside the same storage backend, under a `.versions/<node_id>/<version_n>` prefix.
Snapshots are taken before a destructive write. Every surface that can replace an existing file's bytes calls writehook.BeforeOverwrite first, which lands in GuardOverwrite (guard.go) and snapshots whatever is about to be lost; a snapshot that cannot be taken refuses the write rather than proceeding. save-text snapshots directly through Snapshot instead, because it already holds the node row.
⚠ This paragraph used to claim the same guarantee while it was not true: until the pre-write guard landed, Snapshot was reached from save-text and the versions endpoints and NOWHERE ELSE -- no upload, move, copy, archive extract or AI write path ever called it, so uploading a file over an existing one destroyed the old bytes with no version kept. Do not restate the guarantee here without checking that the call sites still back it.
Listing + restore are exposed via dedicated /api/files/versions endpoints.
Storage drivers that implement storage.Copier get fast server-side copies; anything else falls back to stream-and-rewrite via Read + Write.
Index ¶
- Constants
- func SnapshotSkipped(ctx context.Context) bool
- func WithoutSnapshot(ctx context.Context) context.Context
- type RetentionResult
- type Service
- func (s *Service) AttachBody(b *filebody.Resolver)
- func (s *Service) Cleanup(ctx context.Context, nodeID int64, keepN int) (int, error)
- func (s *Service) GuardOverwrite(ctx context.Context, storageID int64, rel string) error
- func (s *Service) HardDeleteVersion(ctx context.Context, versionID int64) error
- func (s *Service) KeepN(ctx context.Context) int
- func (s *Service) List(ctx context.Context, nodeID int64) ([]*model.NodeVersion, error)
- func (s *Service) Restore(ctx context.Context, nodeID, versionID int64, snapshotCurrent bool) error
- func (s *Service) RunRetentionLoop(ctx context.Context, interval time.Duration)
- func (s *Service) RunRetentionOnce(ctx context.Context) (RetentionResult, error)
- func (s *Service) Snapshot(ctx context.Context, nodeID int64) (*model.NodeVersion, error)
- type StorageResolver
Constants ¶
const DefaultRetention = 20
DefaultRetention is the per-node version count kept by Cleanup if the caller does not specify a different value.
const SettingKeyKeepN = "versions.keep_n"
SettingKeyKeepN is the settings-table row holding the per-node version keep count. 0 = retention job disabled (unlimited).
const VersionsPrefix = syspath.Versions
VersionsPrefix is prepended to each snapshot key. Changing it invalidates existing snapshots; do not change without a migration.
Variables ¶
This section is empty.
Functions ¶
func SnapshotSkipped ¶ added in v0.48.0
SnapshotSkipped reports whether ctx carries WithoutSnapshot.
Types ¶
type RetentionResult ¶
type RetentionResult struct {
KeepN int `json:"keep_n"`
Nodes int `json:"nodes"`
Deleted int `json:"deleted"`
Failed int `json:"failed"`
}
RetentionResult summarises one retention run.
type Service ¶
type Service struct {
Store db.Store
Resolver StorageResolver
// Body resolves where the LIVE file's bytes are before a snapshot: the
// driver, or filex's staging area while a staged upload is still
// transferring. Nil-safe. Without it, snapshotting a file whose overwrite
// is still in flight would capture the version it is replacing.
Body *filebody.Resolver
}
Service is the high-level entry point for versioning operations.
func (*Service) AttachBody ¶
AttachBody wires the byte-source resolver (staging-aware snapshots).
func (*Service) Cleanup ¶
Cleanup deletes versions for a node beyond the keepN newest. It also tries to delete the underlying storage objects but tolerates per-object failures.
func (*Service) GuardOverwrite ¶
GuardOverwrite is the versioning half of writehook.BeforeOverwrite: if a catalogued FILE already lives at rel, snapshot it before the caller replaces it. Returns nil when there is nothing to lose (new file, directory, internal path) so a first write costs one indexed lookup and nothing else.
An error here MUST abort the write — see Snapshot's contract: "losing version history is preferable to corruption".
The bytes are copied verbatim, so a client-side (E2E) encrypted file is snapshotted as the ciphertext it already is: restorable by whoever holds the password, unreadable to the server, and with its `filexe2e` magic prefix intact. The guard never reads content and never touches the folder marker (`.filex-e2e.json`) except to snapshot it like any other file — which is a gain, since a lost marker makes an encrypted folder permanently unopenable.
func (*Service) HardDeleteVersion ¶
HardDeleteVersion drops one version row + its storage object (admin op).
func (*Service) KeepN ¶
KeepN reads the configured per-node keep count. Missing, empty or non-numeric values (and anything < 0) resolve to 0 = disabled.
func (*Service) Restore ¶
Restore copies a recorded version back over the live path.
If snapshotCurrent is true the current live content is first snapshotted so the operation is reversible.
func (*Service) RunRetentionLoop ¶
RunRetentionLoop ticks RunRetentionOnce every interval until ctx is cancelled. First tick happens after `interval`, not immediately (trash.RunDailyLoop pattern).
func (*Service) RunRetentionOnce ¶
func (s *Service) RunRetentionOnce(ctx context.Context) (RetentionResult, error)
RunRetentionOnce applies Cleanup(keepN) to every node that has version rows. A keep_n of 0 is a no-op (disabled). Per-node failures are counted + logged, never fatal — one broken node must not stall the whole sweep.
func (*Service) Snapshot ¶
Snapshot copies the current live storage_key into the .versions/ tree and records a node_versions row. Returns nil with no error when there's nothing to snapshot (node has no live storage_key yet — first ever write).
Callers should invoke this BEFORE a destructive write. If the snapshot itself fails (storage / DB error) the caller should NOT proceed with the destructive write — losing version history is preferable to corruption.