refactor(scanner): make poll interval dynamic from database
- Add GetPollInterval() method to MediaScanner to read from database - Add GetAutoScanEnabled() method to check if auto-scan is enabled - Remove ScanPollIntervalSeconds from config (now DB-driven) - Update NewMediaScanner signature to not require interval parameter - Remove SCAN_POLL_INTERVAL_SECONDS from docker-compose env var
This commit is contained in:
@@ -10,7 +10,6 @@ services:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${DBPASS}
|
||||
COOKIE_SECURE: false # make true in production with HTTPS
|
||||
SCAN_POLL_INTERVAL_SECONDS: 30
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./database/schema:/docker-entrypoint-initdb.d
|
||||
|
||||
+24
-26
@@ -7,37 +7,35 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServerPort string
|
||||
BaseURL string
|
||||
JWTSecret string
|
||||
UploadPath string
|
||||
DatabaseHost string
|
||||
DatabasePort string
|
||||
DatabaseUser string
|
||||
DatabasePassword string
|
||||
DatabaseName string
|
||||
TestMode bool
|
||||
RateLimitEnabled bool
|
||||
RequestsPerMinute int
|
||||
ScanPollIntervalSeconds int `env:"SCAN_POLL_INTERVAL_SECONDS" default:"30"`
|
||||
ServerPort string
|
||||
BaseURL string
|
||||
JWTSecret string
|
||||
UploadPath string
|
||||
DatabaseHost string
|
||||
DatabasePort string
|
||||
DatabaseUser string
|
||||
DatabasePassword string
|
||||
DatabaseName string
|
||||
TestMode bool
|
||||
RateLimitEnabled bool
|
||||
RequestsPerMinute int
|
||||
}
|
||||
|
||||
func LoadConfig() *Config {
|
||||
port := getEnv("SERVER_PORT", "8765")
|
||||
return &Config{
|
||||
ServerPort: port,
|
||||
BaseURL: getEnv("BASE_URL", "http://localhost:"+port),
|
||||
DatabaseHost: getEnv("DATABASE_HOST", "localhost"),
|
||||
DatabasePort: getEnv("DATABASE_PORT", "5432"),
|
||||
DatabaseUser: getEnv("DATABASE_USER", "postgres"),
|
||||
DatabasePassword: getEnv("DATABASE_PASSWORD", "password"),
|
||||
DatabaseName: getEnv("DATABASE_NAME", "bookhoard"),
|
||||
JWTSecret: getEnv("JWT_SECRET", "your-secret-key"),
|
||||
UploadPath: getEnv("UPLOAD_PATH", "./uploads"),
|
||||
TestMode: getEnvBool("TEST_MODE", false),
|
||||
RateLimitEnabled: getEnvBool("RATE_LIMIT_ENABLED", true),
|
||||
RequestsPerMinute: getEnvInt("REQUESTS_PER_MINUTE", 10),
|
||||
ScanPollIntervalSeconds: getEnvInt("SCAN_POLL_INTERVAL_SECONDS", 30),
|
||||
ServerPort: port,
|
||||
BaseURL: getEnv("BASE_URL", "http://localhost:"+port),
|
||||
DatabaseHost: getEnv("DATABASE_HOST", "localhost"),
|
||||
DatabasePort: getEnv("DATABASE_PORT", "5432"),
|
||||
DatabaseUser: getEnv("DATABASE_USER", "postgres"),
|
||||
DatabasePassword: getEnv("DATABASE_PASSWORD", "password"),
|
||||
DatabaseName: getEnv("DATABASE_NAME", "bookhoard"),
|
||||
JWTSecret: getEnv("JWT_SECRET", "your-secret-key"),
|
||||
UploadPath: getEnv("UPLOAD_PATH", "./uploads"),
|
||||
TestMode: getEnvBool("TEST_MODE", false),
|
||||
RateLimitEnabled: getEnvBool("RATE_LIMIT_ENABLED", true),
|
||||
RequestsPerMinute: getEnvInt("REQUESTS_PER_MINUTE", 10),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ type Handler struct {
|
||||
db *database.Queries
|
||||
scanner *services.MediaScanner
|
||||
worker *services.Worker
|
||||
scheduler *services.Scheduler
|
||||
queueProcessor *wsync.SyncQueueProcessor
|
||||
queueCtx context.Context
|
||||
queueCancel context.CancelFunc
|
||||
@@ -34,7 +33,6 @@ type Handler struct {
|
||||
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)
|
||||
scheduler := services.NewScheduler(worker, db)
|
||||
|
||||
queueCtx, queueCancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -42,9 +40,8 @@ func NewHandler(db *database.Queries, connManager *wsync.ConnectionManager, queu
|
||||
|
||||
return &Handler{
|
||||
db: db,
|
||||
scanner: services.NewMediaScanner(db, cfg.ScanPollIntervalSeconds),
|
||||
scanner: services.NewMediaScanner(db),
|
||||
worker: worker,
|
||||
scheduler: scheduler,
|
||||
queueProcessor: queueProcessor,
|
||||
queueCtx: queueCtx,
|
||||
queueCancel: queueCancel,
|
||||
|
||||
@@ -90,18 +90,12 @@ type MediaScanner struct {
|
||||
}
|
||||
|
||||
// NewMediaScanner creates a new media scanner instance
|
||||
func NewMediaScanner(db *database.Queries, pollIntervalSeconds int) *MediaScanner {
|
||||
func NewMediaScanner(db *database.Queries) *MediaScanner {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to create file watcher: %v", err))
|
||||
}
|
||||
|
||||
pollInterval := 30 * time.Second
|
||||
|
||||
if pollInterval > 0 {
|
||||
pollInterval = time.Duration(pollIntervalSeconds) * time.Second
|
||||
}
|
||||
|
||||
return &MediaScanner{
|
||||
db: db,
|
||||
watcher: watcher,
|
||||
@@ -111,10 +105,44 @@ func NewMediaScanner(db *database.Queries, pollIntervalSeconds int) *MediaScanne
|
||||
libraryTypes: make(map[string][]string),
|
||||
logger: NewScannerLogger(),
|
||||
eventQueue: make(chan string, 500),
|
||||
pollInterval: pollInterval,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MediaScanner) GetPollInterval() time.Duration {
|
||||
if s.db == nil {
|
||||
return 30 * time.Second
|
||||
}
|
||||
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
|
||||
}
|
||||
seconds, err := strconv.Atoi(setting)
|
||||
if err != nil || seconds < 1 {
|
||||
return 30 * time.Second
|
||||
}
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
|
||||
func (s *MediaScanner) GetAutoScanEnabled() bool {
|
||||
if s.db == nil {
|
||||
return true // default to enabled
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (s *MediaScanner) SetAdminID(adminID pgtype.UUID) {
|
||||
s.adminID = adminID
|
||||
}
|
||||
@@ -1654,26 +1682,32 @@ func (s *MediaScanner) handleFileDelete(ctx context.Context, filePath string) {
|
||||
}
|
||||
|
||||
func (s *MediaScanner) StartPolling(ctx context.Context) {
|
||||
if s.pollInterval <= 0 {
|
||||
interval := s.GetPollInterval()
|
||||
if interval <= 0 {
|
||||
fmt.Println("Polling fallback disabled (interval = 0")
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(s.pollInterval)
|
||||
defer ticker.Stop()
|
||||
fmt.Printf("Polling fallback started with interval: %v\n", s.pollInterval)
|
||||
fmt.Printf("Polling fallback started with interval: %v\n", interval)
|
||||
|
||||
for {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Println("Polling fallback stopped")
|
||||
return
|
||||
case <-ticker.C:
|
||||
fmt.Println("Running polling fallback sync...")
|
||||
//Re-read interval each tick for dynamic updates
|
||||
interval = s.GetPollInterval()
|
||||
fmt.Printf("Running polling fallback sync (interval: %v)...\n", interval)
|
||||
if err := s.SyncFilesystemWithDatabase(ctx); err != nil {
|
||||
fmt.Printf("Polling sync error: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MediaScanner) SyncFilesystemWithDatabase(ctx context.Context) error {
|
||||
fmt.Println("[POLL-SYNC] Starting filesystem sync with database")
|
||||
for _, folder := range s.folders {
|
||||
|
||||
Reference in New Issue
Block a user