1package services
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8
9 "github.com/bnema/dumber/internal/config"
10 "strings"
11)
12
13// ConfigService handles configuration operations for the application.
14type ConfigService struct {
15 config *config.Config
16 configPath string
17}
18
19const defaultDirPerm = 0o750
20
21// ConfigInfo represents configuration information for the frontend.
22type ConfigInfo struct {
23 ConfigPath string `json:"config_path"`
24 DatabasePath string `json:"database_path"`
25 SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"`
26 DmenuSettings *config.DmenuConfig `json:"dmenu_settings"`
27 RenderingMode string `json:"rendering_mode"`
28}
29
30// NewConfigService creates a new ConfigService instance.
31func NewConfigService(cfg *config.Config, configPath string) *ConfigService {
32 return &ConfigService{
33 config: cfg,
34 configPath: configPath,
35 }
36}
37
38// GetConfigInfo returns comprehensive configuration information.
39func (s *ConfigService) GetConfigInfo(ctx context.Context) (*ConfigInfo, error) {
40 _ = ctx
41 return &ConfigInfo{
42 ConfigPath: s.configPath,
43 DatabasePath: s.config.Database.Path,
44 SearchShortcuts: s.config.SearchShortcuts,
45 DmenuSettings: &s.config.Dmenu,
46 RenderingMode: string(s.config.RenderingMode),
47 }, nil
48}
49
50// GetSearchShortcuts returns all configured search shortcuts.
51func (s *ConfigService) GetSearchShortcuts(ctx context.Context) (map[string]config.SearchShortcut, error) {
52 _ = ctx
53 return s.config.SearchShortcuts, nil
54}
55
56// AddSearchShortcut adds a new search shortcut.
57func (s *ConfigService) AddSearchShortcut(ctx context.Context, key string, shortcut config.SearchShortcut) error {
58 _ = ctx
59 if key == "" {
60 return fmt.Errorf("shortcut key cannot be empty")
61 }
62
63 if shortcut.URL == "" {
64 return fmt.Errorf("shortcut URL cannot be empty")
65 }
66
67 if s.config.SearchShortcuts == nil {
68 s.config.SearchShortcuts = make(map[string]config.SearchShortcut)
69 }
70
71 s.config.SearchShortcuts[key] = shortcut
72 return s.saveConfig()
73}
74
75// UpdateSearchShortcut updates an existing search shortcut.
76func (s *ConfigService) UpdateSearchShortcut(ctx context.Context, key string, shortcut config.SearchShortcut) error {
77 _ = ctx
78 if key == "" {
79 return fmt.Errorf("shortcut key cannot be empty")
80 }
81
82 if _, exists := s.config.SearchShortcuts[key]; !exists {
83 return fmt.Errorf("shortcut '%s' does not exist", key)
84 }
85
86 if shortcut.URL == "" {
87 return fmt.Errorf("shortcut URL cannot be empty")
88 }
89
90 s.config.SearchShortcuts[key] = shortcut
91 return s.saveConfig()
92}
93
94// DeleteSearchShortcut removes a search shortcut.
95func (s *ConfigService) DeleteSearchShortcut(ctx context.Context, key string) error {
96 _ = ctx
97 if key == "" {
98 return fmt.Errorf("shortcut key cannot be empty")
99 }
100
101 if _, exists := s.config.SearchShortcuts[key]; !exists {
102 return fmt.Errorf("shortcut '%s' does not exist", key)
103 }
104
105 delete(s.config.SearchShortcuts, key)
106 return s.saveConfig()
107}
108
109// GetDmenuSettings returns current dmenu settings.
110func (s *ConfigService) GetDmenuSettings(ctx context.Context) (*config.DmenuConfig, error) {
111 _ = ctx
112 return &s.config.Dmenu, nil
113}
114
115// UpdateDmenuSettings updates dmenu configuration.
116func (s *ConfigService) UpdateDmenuSettings(ctx context.Context, settings *config.DmenuConfig) error {
117 _ = ctx
118 if settings == nil {
119 return fmt.Errorf("dmenu settings cannot be nil")
120 }
121
122 s.config.Dmenu = *settings
123 return s.saveConfig()
124}
125
126// GetDatabasePath returns the current database path.
127func (s *ConfigService) GetDatabasePath(ctx context.Context) (string, error) {
128 _ = ctx
129 return s.config.Database.Path, nil
130}
131
132// SetDatabasePath updates the database path.
133func (s *ConfigService) SetDatabasePath(ctx context.Context, path string) error {
134 _ = ctx
135 if path == "" {
136 return fmt.Errorf("database path cannot be empty")
137 }
138
139 // Validate that the directory exists or can be created
140 dir := filepath.Dir(path)
141 if err := os.MkdirAll(dir, defaultDirPerm); err != nil {
142 return fmt.Errorf("failed to create database directory: %w", err)
143 }
144
145 s.config.Database.Path = path
146 return s.saveConfig()
147}
148
149// GetDefaultBrowser returns the default browser setting.
150func (s *ConfigService) GetDefaultBrowser(ctx context.Context) (string, error) {
151 _ = ctx
152 // Default browser is now built into the app
153 return "dumber (built-in)", nil
154}
155
156// SetDefaultBrowser updates the default browser setting.
157func (s *ConfigService) SetDefaultBrowser(ctx context.Context, browser string) error {
158 _ = ctx
159 _ = browser
160 // Default browser setting is not needed for built-in browser
161 return fmt.Errorf("default browser is built-in and cannot be changed")
162}
163
164// ReloadConfig reloads configuration from file.
165func (s *ConfigService) ReloadConfig(ctx context.Context) error {
166 _ = ctx
167 // Create new manager and load config
168 manager, err := config.NewManager()
169 if err != nil {
170 return fmt.Errorf("failed to create config manager: %w", err)
171 }
172
173 if err := manager.Load(); err != nil {
174 return fmt.Errorf("failed to load config: %w", err)
175 }
176
177 s.config = manager.Get()
178 return nil
179}
180
181// ValidateConfig validates the current configuration.
182func (s *ConfigService) ValidateConfig(ctx context.Context) ([]string, error) {
183 _ = ctx
184 var errors []string
185
186 // Validate database path
187 if s.config.Database.Path == "" {
188 errors = append(errors, "database path is not set")
189 } else {
190 dir := filepath.Dir(s.config.Database.Path)
191 if _, err := os.Stat(dir); os.IsNotExist(err) {
192 errors = append(errors, fmt.Sprintf("database directory does not exist: %s", dir))
193 }
194 }
195
196 // Validate search shortcuts
197 if len(s.config.SearchShortcuts) == 0 {
198 errors = append(errors, "no search shortcuts configured")
199 } else {
200 for key, shortcut := range s.config.SearchShortcuts {
201 if shortcut.URL == "" {
202 errors = append(errors, fmt.Sprintf("search shortcut '%s' has empty URL", key))
203 }
204 }
205 }
206
207 // Check for default search shortcut
208 if _, hasDefault := s.config.SearchShortcuts["g"]; !hasDefault {
209 errors = append(errors, "no default search shortcut 'g' configured")
210 }
211
212 // Validate dmenu settings
213 if s.config.Dmenu.MaxHistoryItems < 1 {
214 errors = append(errors, "dmenu max_history_items must be at least 1")
215 }
216
217 return errors, nil
218}
219
220// GetConfigPath returns the path to the configuration file.
221func (s *ConfigService) GetConfigPath(ctx context.Context) (string, error) {
222 _ = ctx
223 return s.configPath, nil
224}
225
226// ExportConfig returns the current configuration as JSON.
227func (s *ConfigService) ExportConfig(ctx context.Context) (*config.Config, error) {
228 _ = ctx
229 // Return a copy of the config to prevent modifications
230 configCopy := *s.config
231 return &configCopy, nil
232}
233
234// ImportConfig imports configuration from a Config object.
235func (s *ConfigService) ImportConfig(ctx context.Context, newConfig *config.Config) error {
236 if newConfig == nil {
237 return fmt.Errorf("config cannot be nil")
238 }
239
240 // Validate the new config
241 tempService := &ConfigService{config: newConfig}
242 validationErrors, err := tempService.ValidateConfig(ctx)
243 if err != nil {
244 return fmt.Errorf("failed to validate config: %w", err)
245 }
246
247 if len(validationErrors) > 0 {
248 return fmt.Errorf("config validation failed: %v", validationErrors)
249 }
250
251 // Apply the new config
252 s.config = newConfig
253 return s.saveConfig()
254}
255
256// GetRenderingMode returns the current rendering mode (auto|gpu|cpu).
257func (s *ConfigService) GetRenderingMode(ctx context.Context) (string, error) {
258 _ = ctx
259 return string(s.config.RenderingMode), nil
260}
261
262// SetRenderingMode updates the rendering mode (auto|gpu|cpu).
263func (s *ConfigService) SetRenderingMode(ctx context.Context, mode string) error {
264 _ = ctx
265 m := strings.ToLower(strings.TrimSpace(mode))
266 switch m {
267 case "auto", "gpu", "cpu":
268 s.config.RenderingMode = config.RenderingMode(m)
269 return s.saveConfig()
270 default:
271 return fmt.Errorf("invalid rendering mode: %s", mode)
272 }
273}
274
275// saveConfig saves the current configuration to file.
276func (s *ConfigService) saveConfig() error {
277 // For now, return success - implementing config saving would require
278 // extending the config package with save functionality
279 return nil
280}