Trove

Local Filesystem Driver

Store objects on the local filesystem with atomic writes and sidecar metadata.

The local filesystem driver stores objects as files on disk. Buckets are directories, objects are files, and metadata is stored in .meta.json sidecar files alongside each object.

Installation

go get github.com/xraph/trove/drivers/localdriver

Usage

import (
    "context"
    "github.com/xraph/trove"
    "github.com/xraph/trove/drivers/localdriver"
)

ctx := context.Background()

// Create the driver
drv := localdriver.New()

// Open with a file:// DSN
err := drv.Open(ctx, "file:///var/data/trove")
if err != nil {
    log.Fatal(err)
}

// Use with Trove
t, err := trove.Open(drv)

DSN Format

The local driver supports two URI schemes:

file:///absolute/path/to/root          # Absolute path only
local://./relative/path                # Relative path (resolved from cwd)
local:///absolute/path                 # Absolute path

The file:// scheme requires an absolute path. The local:// scheme supports both relative and absolute paths. Relative paths are resolved to absolute from the current working directory.

Examples

# Absolute path
storage_driver: "file:///var/data/trove"

# Relative path (resolved from cwd)
storage_driver: "local://./storages/local"

# Absolute path with local:// scheme
storage_driver: "local:///data/archive"

Driver Registry

The local driver registers itself in the global driver registry under both file and local schemes. This enables DSN-based driver resolution in multi-store mode:

import _ "github.com/xraph/trove/drivers/localdriver" // registers "file" and "local"

Storage Layout

/var/data/trove/           <-- root directory
├── photos/                <-- bucket "photos"
│   ├── beach.jpg          <-- object "beach.jpg"
│   ├── .beach.jpg.meta.json    <-- metadata sidecar
│   └── sunset.png
└── documents/             <-- bucket "documents"
    ├── report.pdf
    └── .report.pdf.meta.json

Features

Atomic Writes

Put operations use write-to-temp-then-rename for crash safety. Objects are first written to a temporary file, then atomically renamed to the final path. This prevents partial writes from corrupting existing objects.

Sidecar Metadata

Each object can have an associated .meta.json sidecar file that stores:

  • Content type
  • Custom metadata key-value pairs

Content Type Detection

If no content type is specified via driver.WithContentType(), the driver detects it from the file extension using Go's mime package.

Directory-Based Buckets

Buckets map directly to subdirectories under the root. Creating a bucket creates a directory; deleting a bucket removes the directory.

API

Constructor

func New() *LocalDriver

Creates a new local filesystem driver.

SetRootDir / RootDir

drv := localdriver.New()
drv.SetRootDir("/tmp/test-data")
fmt.Println(drv.RootDir()) // /tmp/test-data

Set the root directory directly (alternative to using Open with a DSN).

Unwrap

Extract the *LocalDriver from a Trove handle for direct access:

func Unwrap(accessor interface{ Driver() driver.Driver }) *LocalDriver
local := localdriver.Unwrap(troveInstance)
if local != nil {
    fmt.Println("Root:", local.RootDir())
}

Example: File Upload Server

func handleUpload(w http.ResponseWriter, r *http.Request) {
    file, header, _ := r.FormFile("file")
    defer file.Close()

    info, err := store.Put(r.Context(), "uploads", header.Filename, file,
        driver.WithContentType(header.Header.Get("Content-Type")),
    )
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    fmt.Fprintf(w, "Uploaded: %s (%d bytes)", info.Key, info.Size)
}

Limitations

  • No server-side copy across different root directories
  • No multipart upload support (entire file is written at once)
  • No pre-signed URLs
  • Metadata is limited to what .meta.json sidecar files store
  • Not suitable for high-concurrency production workloads (use S3/GCS drivers instead)

On this page