Lifecycle (internal/app + main.go): - App.OnShutdown hooks run after the HTTP server drains, in registration order: Worker.Shutdown (background jobs now drain instead of dying on SIGTERM), DeviceAuthMiddleware.Stop (releases the rate-limiter ticker), Handler.CloseScanner (fsnotify watcher and scan log files) Security headers: - SecurityHeadersMiddleware (survivor of middleware/security.go, renamed security_headers.go) is now on the echo stack: nosniff, frame-deny, HSTS, CSP (unsafe-inline required by the sanctioned theme flash guard + Alpine), referrer and permissions policy Scanner settings cache: - settings saves (UpdateScanSettings + ApplySetting for the three scan keys) now invalidate MediaScanner's 30s settings cache via SystemSettingsHandler.SetScanSettingsChanged, so changes apply on the next poll instead of after the TTL - MediaScanner.Close closes the scan logger, fixing a file-descriptor leak on every per-item rescan (fresh scanners never closed logs) Client fix: - custom-section-builder's add/remove-selection buttons used inline onclick attributes referencing module-scoped functions, which are not global in a bundled ES module: the buttons threw ReferenceError. Functions restored and exposed on window via declare global Also fixes a pre-existing vet error (saveCoverImage copied echo.Context by value, mutex included) and drops the per-key SettingsCache.InvalidateKey variant left callerless by the full-flush invalidation wiring.
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bookhoard/internal/config"
|
|
"bookhoard/internal/database"
|
|
"bookhoard/internal/services"
|
|
wsync "bookhoard/internal/sync"
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
)
|
|
|
|
type Handler struct {
|
|
db *database.Queries
|
|
scanner *services.MediaScanner
|
|
worker *services.Worker
|
|
queueProcessor *wsync.SyncQueueProcessor
|
|
queueCtx context.Context
|
|
queueCancel context.CancelFunc
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
mu sync.Mutex
|
|
watchModeCtx context.Context
|
|
watchModeCancel context.CancelFunc
|
|
watchingLibraries map[string]bool
|
|
connManager *wsync.ConnectionManager
|
|
cleanupTaskCancel context.CancelFunc
|
|
config *config.Config
|
|
}
|
|
|
|
func NewHandler(db *database.Queries, connManager *wsync.ConnectionManager, queueProcessor *wsync.SyncQueueProcessor, cfg *config.Config) *Handler {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
worker := services.NewWorker(3, connManager)
|
|
|
|
queueCtx, queueCancel := context.WithCancel(context.Background())
|
|
|
|
watchCtx, watchCancel := context.WithCancel(context.Background())
|
|
|
|
return &Handler{
|
|
db: db,
|
|
scanner: services.NewMediaScanner(db),
|
|
worker: worker,
|
|
queueProcessor: queueProcessor,
|
|
queueCtx: queueCtx,
|
|
queueCancel: queueCancel,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
watchModeCtx: watchCtx,
|
|
watchModeCancel: watchCancel,
|
|
watchingLibraries: make(map[string]bool),
|
|
connManager: connManager,
|
|
}
|
|
}
|
|
|
|
// CloseScanner releases scanner resources (fsnotify watcher, scan log files).
|
|
// Call once during graceful shutdown.
|
|
func (h *Handler) CloseScanner() {
|
|
if h.scanner != nil {
|
|
_ = h.scanner.Close()
|
|
}
|
|
}
|
|
|
|
// InvalidateScannerSettingsCache drops the scanner's cached system settings so
|
|
// saved scan settings take effect on the next poll instead of after the TTL.
|
|
func (h *Handler) InvalidateScannerSettingsCache() {
|
|
if h.scanner != nil {
|
|
h.scanner.InvalidateSettingsCache()
|
|
}
|
|
}
|
|
|
|
// parseDate parses a date string in YYYY-MM-DD format
|
|
func parseDate(dateStr string) time.Time {
|
|
if dateStr == "" {
|
|
return time.Time{}
|
|
}
|
|
if t, err := time.Parse("2006-01-02", dateStr); err == nil {
|
|
return t
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// normalizeNarrators guards the NOT NULL narrators columns (A2): a Go nil
|
|
// slice becomes an explicit NULL in INSERT/UPDATE params and 23502s — the
|
|
// column DEFAULT never fires for explicit NULLs. Omitted narrators mean
|
|
// "none known", which is an empty array, never NULL.
|
|
func normalizeNarrators(narrators []string) []string {
|
|
if narrators == nil {
|
|
return []string{}
|
|
}
|
|
return narrators
|
|
}
|
|
|
|
func SetupRoutes(g *echo.Group, db *database.Queries, connManager *wsync.ConnectionManager, queueProcessor *wsync.SyncQueueProcessor, cfg *config.Config) *Handler {
|
|
return NewHandler(db, connManager, queueProcessor, cfg)
|
|
}
|