1// Package config provides configuration management for dumber with Viper integration.
2package config
3
4import (
5 "os"
6 "path/filepath"
7
8 "github.com/bnema/dumber/internal/infrastructure/runtimeprofile"
9)
10
11// File permission constants
12const (
13 dirPerm = 0755 // Standard directory permissions (rwxr-xr-x)
14 filePerm = 0644 // Standard file permissions (rw-r--r--)
15)
16
17const (
18 appName = "dumber"
19 databaseName = "dumber.db"
20)
21
22// XDGDirs holds the XDG Base Directory paths for the application.
23type XDGDirs struct {
24 ConfigHome string
25 DataHome string
26 StateHome string
27 CacheHome string
28}
29
30// GetXDGDirs returns the dumber-specific XDG directories.
31// In prod they match the current XDG layout exactly.
32// In dev they resolve into the shared sandbox under .dev/dumber/{config,data,state,cache}.
33func GetXDGDirs() (*XDGDirs, error) {
34 profile, err := resolveSharedProfile()
35 if err != nil {
36 return nil, err
37 }
38 return &XDGDirs{
39 ConfigHome: profile.Shared.ConfigDir,
40 DataHome: profile.Shared.DataDir,
41 StateHome: profile.Shared.StateDir,
42 CacheHome: profile.Shared.CacheDir,
43 }, nil
44}
45
46func resolveSharedProfile() (runtimeprofile.Profile, error) {
47 base, err := getBaseXDGDirs()
48 if err != nil {
49 return runtimeprofile.Profile{}, err
50 }
51 return runtimeprofile.Resolve(runtimeprofile.ResolveInput{
52 Env: os.Getenv,
53 CWD: os.Getwd,
54 Base: runtimeprofile.BasePaths{
55 ConfigHome: base.ConfigHome,
56 DataHome: base.DataHome,
57 StateHome: base.StateHome,
58 CacheHome: base.CacheHome,
59 },
60 })
61}
62
63func getBaseXDGDirs() (*XDGDirs, error) {
64 if os.Getenv("ENV") == "dev" {
65 return &XDGDirs{}, nil
66 }
67
68 homeDir, err := os.UserHomeDir()
69 if err != nil {
70 return nil, err
71 }
72
73 configHome := os.Getenv("XDG_CONFIG_HOME")
74 if configHome == "" {
75 configHome = filepath.Join(homeDir, ".config")
76 }
77 configHome = filepath.Join(configHome, appName)
78
79 dataHome := os.Getenv("XDG_DATA_HOME")
80 if dataHome == "" {
81 dataHome = filepath.Join(homeDir, ".local", "share")
82 }
83 dataHome = filepath.Join(dataHome, appName)
84
85 stateHome := os.Getenv("XDG_STATE_HOME")
86 if stateHome == "" {
87 stateHome = filepath.Join(homeDir, ".local", "state")
88 }
89 stateHome = filepath.Join(stateHome, appName)
90
91 cacheHome := os.Getenv("XDG_CACHE_HOME")
92 if cacheHome == "" {
93 cacheHome = filepath.Join(homeDir, ".cache")
94 }
95 cacheHome = filepath.Join(cacheHome, appName)
96
97 return &XDGDirs{
98 ConfigHome: configHome,
99 DataHome: dataHome,
100 StateHome: stateHome,
101 CacheHome: cacheHome,
102 }, nil
103}
104
105// GetConfigDir returns the XDG config directory for dumber.
106func GetConfigDir() (string, error) {
107 dirs, err := GetXDGDirs()
108 if err != nil {
109 return "", err
110 }
111 return dirs.ConfigHome, nil
112}
113
114// GetDataDir returns the XDG data directory for dumber.
115func GetDataDir() (string, error) {
116 dirs, err := GetXDGDirs()
117 if err != nil {
118 return "", err
119 }
120 return dirs.DataHome, nil
121}
122
123// GetStateDir returns the XDG state directory for dumber.
124func GetStateDir() (string, error) {
125 dirs, err := GetXDGDirs()
126 if err != nil {
127 return "", err
128 }
129 return dirs.StateHome, nil
130}
131
132// GetLogDir returns the shared application log directory for dumber.
133func GetLogDir() (string, error) {
134 profile, err := resolveSharedProfile()
135 if err != nil {
136 return "", err
137 }
138 return profile.Shared.LogDir, nil
139}
140
141// GetConfigFile returns the path to the main configuration file.
142func GetConfigFile() (string, error) {
143 configDir, err := GetConfigDir()
144 if err != nil {
145 return "", err
146 }
147 return filepath.Join(configDir, "config.toml"), nil
148}
149
150// GetDatabaseFile returns the path to the database file in the data directory.
151// The database contains user data (history, favorites, preferences) that should
152// be backed up, so it belongs in XDG_DATA_HOME per the XDG specification.
153func GetDatabaseFile() (string, error) {
154 dataDir, err := GetDataDir()
155 if err != nil {
156 return "", err
157 }
158 return filepath.Join(dataDir, databaseName), nil
159}
160
161// GetFilterCacheDir returns the XDG-compliant filter cache directory for dumber.
162// Filter cache is stored in XDG_STATE_HOME as it's transient data that can be regenerated.
163func GetFilterCacheDir() (string, error) {
164 stateDir, err := GetStateDir()
165 if err != nil {
166 return "", err
167 }
168 return filepath.Join(stateDir, "filter-cache"), nil
169}
170
171// GetFilterCacheFile returns the path to the main filter cache file.
172func GetFilterCacheFile() (string, error) {
173 cacheDir, err := GetFilterCacheDir()
174 if err != nil {
175 return "", err
176 }
177 return filepath.Join(cacheDir, "filters.cache"), nil
178}
179
180// GetFaviconCacheDir returns the XDG-compliant favicon cache directory for dumber.
181// Favicon cache is stored in XDG_CACHE_HOME as it's transient data that can be refetched.
182func GetFaviconCacheDir() (string, error) {
183 dirs, err := GetXDGDirs()
184 if err != nil {
185 return "", err
186 }
187 return filepath.Join(dirs.CacheHome, "favicons"), nil
188}
189
190// EnsureDirectories creates the XDG directories if they don't exist.
191func EnsureDirectories() error {
192 dirs, err := GetXDGDirs()
193 if err != nil {
194 return err
195 }
196
197 // Create all directories with proper permissions
198 directories := []string{
199 dirs.ConfigHome,
200 dirs.DataHome,
201 dirs.StateHome,
202 dirs.CacheHome,
203 }
204
205 for _, dir := range directories {
206 if err := os.MkdirAll(dir, dirPerm); err != nil {
207 return err
208 }
209 }
210
211 return nil
212}