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	"runtime"
 12	"time"
 13)
 14
 15const (
 16	defaultNamespace = "nasin-pali"
 17
 18	// defaultTxnMaxRetries is the number of times we retry transactional conflicts.
 19	defaultTxnMaxRetries = 5
 20
 21	// defaultConflictBackoff is the initial delay used when backing off after a conflict.
 22	defaultConflictBackoff = 10 * time.Millisecond
 23)
 24
 25// Options captures configuration for opening a database.
 26type Options struct {
 27	// Path is the filesystem path to the Badger data directory. When empty, the
 28	// default path below $XDG_CONFIG_HOME (or platform equivalent) is used.
 29	Path string
 30
 31	// ReadOnly toggles read-only mode. Attempts to perform write transactions in
 32	// this mode will fail.
 33	ReadOnly bool
 34
 35	// SyncWrites mirrors badger.Options.SyncWrites. Default is false to favor
 36	// throughput while accepting a small durability risk.
 37	SyncWrites bool
 38
 39	// Logger receives Badger log messages. Defaults to a no-op logger, as CLI
 40	// workflows prefer explicit output.
 41	Logger Logger
 42
 43	// MaxTxnRetries controls how many times Update() will retry transactions that
 44	// fail with a conflict. Must be >= 0.
 45	MaxTxnRetries int
 46
 47	// ConflictBackoff overrides the base backoff duration between retries. When
 48	// zero, a sensible default is used.
 49	ConflictBackoff time.Duration
 50}
 51
 52// Logger is a simple logging interface that mirrors Badger's expectations while
 53// keeping this package decoupled from the concrete implementation.
 54type Logger interface {
 55	Errorf(format string, args ...any)
 56	Warningf(format string, args ...any)
 57	Infof(format string, args ...any)
 58	Debugf(format string, args ...any)
 59}
 60
 61type noopLogger struct{}
 62
 63func (noopLogger) Errorf(string, ...any)   {}
 64func (noopLogger) Warningf(string, ...any) {}
 65func (noopLogger) Infof(string, ...any)    {}
 66func (noopLogger) Debugf(string, ...any)   {}
 67
 68func (o Options) applyDefaults() (Options, error) {
 69	if o.Path == "" {
 70		path, err := DefaultPath()
 71		if err != nil {
 72			return Options{}, err
 73		}
 74		o.Path = path
 75	}
 76
 77	if o.Logger == nil {
 78		o.Logger = noopLogger{}
 79	}
 80
 81	if o.MaxTxnRetries < 0 {
 82		return Options{}, errors.New("db: MaxTxnRetries must be >= 0")
 83	}
 84	if o.MaxTxnRetries == 0 {
 85		o.MaxTxnRetries = defaultTxnMaxRetries
 86	}
 87
 88	if o.ConflictBackoff <= 0 {
 89		o.ConflictBackoff = defaultConflictBackoff
 90	}
 91
 92	return o, nil
 93}
 94
 95// DefaultPath resolves the directory for persistent data. The location follows
 96// the XDG Base Directory specification when possible and falls back to a
 97// platform-appropriate default.
 98func DefaultPath() (string, error) {
 99	configRoot := os.Getenv("XDG_CONFIG_HOME")
100	if configRoot == "" {
101		home, err := os.UserHomeDir()
102		if err != nil {
103			return "", err
104		}
105		switch runtime.GOOS {
106		case "windows":
107			configRoot = filepath.Join(home, "AppData", "Roaming")
108		case "darwin":
109			configRoot = filepath.Join(home, "Library", "Application Support")
110		default:
111			configRoot = filepath.Join(home, ".config")
112		}
113	}
114
115	return filepath.Join(configRoot, defaultNamespace), nil
116}