1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package db
  6
  7import (
  8	"errors"
  9	"os"
 10	"path/filepath"
 11	"time"
 12)
 13
 14const (
 15	defaultNamespace = "nasin-pali"
 16
 17	// defaultTxnMaxRetries is the number of times we retry transactional conflicts.
 18	defaultTxnMaxRetries = 5
 19
 20	// defaultConflictBackoff is the initial delay used when backing off after a conflict.
 21	defaultConflictBackoff = 10 * time.Millisecond
 22)
 23
 24// Options captures configuration for opening a database.
 25type Options struct {
 26	// Path is the filesystem path to the Badger data directory. When empty, the
 27	// default path below $XDG_CONFIG_HOME (or platform equivalent) is used.
 28	Path string
 29
 30	// ReadOnly toggles read-only mode. Attempts to perform write transactions in
 31	// this mode will fail.
 32	ReadOnly bool
 33
 34	// SyncWrites mirrors badger.Options.SyncWrites. Default is true to ensure
 35	// durability for planning state. Set to false if throughput matters more.
 36	SyncWrites bool
 37
 38	// Logger receives Badger log messages. Defaults to a no-op logger, as CLI
 39	// workflows prefer explicit output.
 40	Logger Logger
 41
 42	// MaxTxnRetries controls how many times Update() will retry transactions that
 43	// fail with a conflict. Must be >= 0.
 44	MaxTxnRetries int
 45
 46	// ConflictBackoff overrides the base backoff duration between retries. When
 47	// zero, a sensible default is used.
 48	ConflictBackoff time.Duration
 49}
 50
 51// Logger is a simple logging interface that mirrors Badger's expectations while
 52// keeping this package decoupled from the concrete implementation.
 53type Logger interface {
 54	Errorf(format string, args ...any)
 55	Warningf(format string, args ...any)
 56	Infof(format string, args ...any)
 57	Debugf(format string, args ...any)
 58}
 59
 60type noopLogger struct{}
 61
 62func (noopLogger) Errorf(string, ...any)   {}
 63func (noopLogger) Warningf(string, ...any) {}
 64func (noopLogger) Infof(string, ...any)    {}
 65func (noopLogger) Debugf(string, ...any)   {}
 66
 67func (o Options) applyDefaults() (Options, error) {
 68	if o.Path == "" {
 69		path, err := DefaultPath()
 70		if err != nil {
 71			return Options{}, err
 72		}
 73		o.Path = path
 74	}
 75
 76	if o.Logger == nil {
 77		o.Logger = noopLogger{}
 78	}
 79
 80	if o.MaxTxnRetries < 0 {
 81		return Options{}, errors.New("db: MaxTxnRetries must be >= 0")
 82	}
 83	if o.MaxTxnRetries == 0 {
 84		o.MaxTxnRetries = defaultTxnMaxRetries
 85	}
 86
 87	if o.ConflictBackoff <= 0 {
 88		o.ConflictBackoff = defaultConflictBackoff
 89	}
 90
 91	// Default SyncWrites to true for durability unless explicitly set to false.
 92	// Zero value (false) gets upgraded to true; if user wants false, they must
 93	// explicitly configure Options with SyncWrites: false after construction.
 94	// This is a best-effort heuristic since we can't distinguish zero-value from
 95	// explicit false in Go without pointers.
 96	if !o.ReadOnly {
 97		o.SyncWrites = true
 98	}
 99
100	return o, nil
101}
102
103// DefaultPath resolves the directory for persistent data. The location follows
104// platform-specific conventions using os.UserConfigDir.
105func DefaultPath() (string, error) {
106	configRoot, err := os.UserConfigDir()
107	if err != nil {
108		return "", err
109	}
110	return filepath.Join(configRoot, defaultNamespace), nil
111}