scanner: fix library isolation, file mtime, force rescan, and deletion handling
Fix 1 - File modification time for created_at: - Get file.ModTime() in processMediaFile and pass to CreateMediaItem - Modified SQL INSERT to include created_at column Fix 2 - Force rescan UPDATE instead of DELETE+INSERT: - Changed force rescan logic to call updateMediaItem instead of delete + create - Preserves created_at timestamp on force rescan Fix 3 - GetMediaItemByFilePath filters by library_id: - Added library_id to WHERE clause in SQL query - Created GetMediaItemByFilePathAnyLibrary for cross-library lookups (KOReader) - Added SetLibraryID method to MediaScanner - Updated handler to call SetLibraryID for watch mode Fix 4 - File deletion handling with persistent logging: - Added fsnotify.Remove handler in WatchChanges - Added orphan cleanup in ScanFolders after scan completes - Created scanner_logger.go with daily log rotation (7 days) - Logs to /app/logs/scanner-deletes-YYYY-MM-DD.log and scanner-errors-YYYY-MM-DD.log - Individual deletes with enhanced safety logging Note: Integration tests can now safely scan /app/uploads because GetMediaItemByFilePath now filters by library_id, preventing cross-library interference.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
logDir = "/app/logs"
|
||||
maxLogAgeDays = 7
|
||||
)
|
||||
|
||||
type ScannerLogger struct {
|
||||
deletesFile *os.File
|
||||
errorsFile *os.File
|
||||
currentDate string
|
||||
}
|
||||
|
||||
func NewScannerLogger() *ScannerLogger {
|
||||
return &ScannerLogger{}
|
||||
}
|
||||
|
||||
func (l *ScannerLogger) ensureLogFiles() error {
|
||||
today := time.Now().Format("2006-01-02")
|
||||
|
||||
if l.currentDate == today && l.deletesFile != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if l.deletesFile != nil {
|
||||
l.deletesFile.Close()
|
||||
}
|
||||
if l.errorsFile != nil {
|
||||
l.errorsFile.Close()
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create log directory: %v", err)
|
||||
}
|
||||
|
||||
deletesPath := filepath.Join(logDir, fmt.Sprintf("scanner-deletes-%s.log", today))
|
||||
errorsPath := filepath.Join(logDir, fmt.Sprintf("scanner-errors-%s.log", today))
|
||||
|
||||
deletesFile, err := os.OpenFile(deletesPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open deletes log file: %v", err)
|
||||
}
|
||||
|
||||
errorsFile, err := os.OpenFile(errorsPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
deletesFile.Close()
|
||||
return fmt.Errorf("failed to open errors log file: %v", err)
|
||||
}
|
||||
|
||||
l.deletesFile = deletesFile
|
||||
l.errorsFile = errorsFile
|
||||
l.currentDate = today
|
||||
|
||||
l.cleanupOldLogs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ScannerLogger) cleanupOldLogs() {
|
||||
cutoff := time.Now().AddDate(0, 0, -maxLogAgeDays)
|
||||
|
||||
filepath.Walk(logDir, func(path string, info os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if !info.IsDir() && info.ModTime().Before(cutoff) {
|
||||
os.Remove(path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (l *ScannerLogger) LogDelete(message string) {
|
||||
if err := l.ensureLogFiles(); err != nil {
|
||||
fmt.Printf("ERROR: Failed to ensure log files: %v\n", err)
|
||||
return
|
||||
}
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
logLine := fmt.Sprintf("[%s] %s\n", timestamp, message)
|
||||
l.deletesFile.WriteString(logLine)
|
||||
}
|
||||
|
||||
func (l *ScannerLogger) LogError(message string) {
|
||||
if err := l.ensureLogFiles(); err != nil {
|
||||
fmt.Printf("ERROR: Failed to ensure log files: %v\n", err)
|
||||
return
|
||||
}
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
logLine := fmt.Sprintf("[%s] %s\n", timestamp, message)
|
||||
l.errorsFile.WriteString(logLine)
|
||||
}
|
||||
|
||||
func (l *ScannerLogger) Close() {
|
||||
if l.deletesFile != nil {
|
||||
l.deletesFile.Close()
|
||||
}
|
||||
if l.errorsFile != nil {
|
||||
l.errorsFile.Close()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user