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}