diff --git a/internal/services/cache.go b/internal/services/cache.go new file mode 100644 index 0000000..21fa541 --- /dev/null +++ b/internal/services/cache.go @@ -0,0 +1,57 @@ +package services + +import ( + "sync" + "time" +) + +type SettingsCache struct { + data map[string]string + mu sync.RWMutex + ttl time.Duration + lastUpdate time.Time +} + +func NewSettingsCache(ttl time.Duration) *SettingsCache { + return &SettingsCache{ + data: make(map[string]string), + ttl: ttl, + lastUpdate: time.Now(), + } +} + +func (c *SettingsCache) Get(key string) (string, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + // Check if cache is expired + if time.Since(c.lastUpdate) > c.ttl { + return "", false + } + + val, ok := c.data[key] + return val, ok +} + +func (c *SettingsCache) Set(key, value string) { + c.mu.Lock() + defer c.mu.Unlock() + + c.data[key] = value + c.lastUpdate = time.Now() +} + +func (c *SettingsCache) Invalidate() { + c.mu.Lock() + defer c.mu.Unlock() + + c.data = make(map[string]string) + c.lastUpdate = time.Time{} +} + +func (c *SettingsCache) InvalidateKey(key string) { + c.mu.Lock() + defer c.mu.Unlock() + + delete(c.data, key) +} diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index 722e208..ecdd484 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -89,6 +89,7 @@ type MediaScanner struct { scan_mutex sync.Mutex pollInterval time.Duration watching atomic.Bool + settingsCache *SettingsCache totalFiles int newItems int @@ -106,6 +107,7 @@ func NewMediaScanner(db *database.Queries) *MediaScanner { return &MediaScanner{ db: db, watcher: watcher, + settingsCache: NewSettingsCache(30 * time.Second), dirtyDirs: make(map[string]time.Time), fileStability: make(map[string]*atomic.Bool), pollInterval: 60 * time.Second, @@ -119,38 +121,52 @@ func NewMediaScanner(db *database.Queries) *MediaScanner { } func (s *MediaScanner) GetPollInterval() time.Duration { - if s.db == nil { - return 30 * time.Second + // Check cache first + if cached, ok := s.settingsCache.Get("scan_poll_interval_seconds"); ok { + if seconds, err := strconv.Atoi(cached); err == nil { + return time.Duration(seconds) * time.Second + } } + + // Cache miss - query database ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() setting, err := s.db.GetSystemSetting(ctx, "scan_poll_interval_seconds") if err != nil || setting == "" { - return 30 * time.Second + return 60 * time.Second } + + // Store in cache + s.settingsCache.Set("scan_poll_interval_seconds", setting) + + // Convert to duration seconds, err := strconv.Atoi(setting) - if err != nil || seconds < 1 { - return 30 * time.Second + if err != nil { + return 60 * time.Second } return time.Duration(seconds) * time.Second } func (s *MediaScanner) GetAutoScanEnabled() bool { - if s.db == nil { - return true // default to enabled + // Check cache first + if cached, ok := s.settingsCache.Get("auto_scan_enabled"); ok { + return strings.ToLower(cached) == "true" } + + // Cache miss - query database ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + setting, err := s.db.GetSystemSetting(ctx, "auto_scan_enabled") if err != nil || setting == "" { return true } - enabled, err := strconv.ParseBool(setting) - if err != nil { - return true - } - return enabled + + // Store in cache + s.settingsCache.Set("auto_scan_enabled", setting) + + return strings.ToLower(setting) == "true" } func (s *MediaScanner) SetAdminID(adminID pgtype.UUID) {