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

versioning

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 16 Imported by: 0

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

View Source
const DefaultRetention = 20

DefaultRetention is the per-node version count kept by Cleanup if the caller does not specify a different value.

View Source
const SettingKeyKeepN = "versions.keep_n"

SettingKeyKeepN is the settings-table row holding the per-node version keep count. 0 = retention job disabled (unlimited).

View Source
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 Forget added in v0.50.0

func Forget(ctx context.Context, drv storage.Driver, nodeID int64, keys []string)

Forget deletes nodeID's snapshot objects (keys, from Keys) from drv, then the node's folder under `.versions/` once nothing is left in it. Best effort: the row is already gone, so a failure is logged and leaves an orphan behind, never an error for the caller.

⚠ Only keys inside the node's own `.versions/<id>/` folder are deleted. The key comes from a database row; a row that named anything else - a live file, another node's history - is not something this cleanup may destroy.

func Keys added in v0.50.0

func Keys(ctx context.Context, store db.Store, nodeID int64) []string

Keys returns the storage keys of nodeID's snapshots. Read it BEFORE the node's row is deleted: the version rows go with it.

func SnapshotSkipped added in v0.48.0

func SnapshotSkipped(ctx context.Context) bool

SnapshotSkipped reports whether ctx carries WithoutSnapshot.

func WithoutSnapshot added in v0.48.0

func WithoutSnapshot(ctx context.Context) context.Context

WithoutSnapshot marks ctx: the overwrite it guards keeps no version.

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 New

func New(store db.Store, resolver StorageResolver) *Service

New constructs a Service.

func (*Service) AttachBody

func (s *Service) AttachBody(b *filebody.Resolver)

AttachBody wires the byte-source resolver (staging-aware snapshots).

func (*Service) Cleanup

func (s *Service) Cleanup(ctx context.Context, nodeID int64, keepN int) (int, error)

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

func (s *Service) GuardOverwrite(ctx context.Context, storageID int64, rel string) error

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

func (s *Service) HardDeleteVersion(ctx context.Context, versionID int64) error

HardDeleteVersion drops one version row + its storage object (admin op).

func (*Service) KeepN

func (s *Service) KeepN(ctx context.Context) int

KeepN reads the configured per-node keep count. Missing, empty or non-numeric values (and anything < 0) resolve to 0 = disabled.

func (*Service) List

func (s *Service) List(ctx context.Context, nodeID int64) ([]*model.NodeVersion, error)

List returns all versions for one node, newest-first.

func (*Service) Restore

func (s *Service) Restore(ctx context.Context, nodeID, versionID int64, snapshotCurrent bool) error

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

func (s *Service) RunRetentionLoop(ctx context.Context, interval time.Duration)

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

func (s *Service) Snapshot(ctx context.Context, nodeID int64) (*model.NodeVersion, error)

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.

type StorageResolver

type StorageResolver func(int64) (storage.Driver, error)

StorageResolver maps a storage_id to a live driver. Same shape as the resolver used by the rest of the API layer.

Jump to

Keyboard shortcuts

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