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