diff --git a/deploy/docker/Dockerfile.master-api b/deploy/docker/Dockerfile.master-api index b908901..9d0812d 100644 --- a/deploy/docker/Dockerfile.master-api +++ b/deploy/docker/Dockerfile.master-api @@ -7,8 +7,8 @@ WORKDIR /src RUN apk add --no-cache git ca-certificates COPY node/media-node/ ./ RUN go mod tidy \ - && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=1.2.2" -o /media-node-linux-amd64 ./cmd/media-node \ - && CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=1.2.2" -o /media-node-linux-arm64 ./cmd/media-node + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=1.2.3" -o /media-node-linux-amd64 ./cmd/media-node \ + && CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=1.2.3" -o /media-node-linux-arm64 ./cmd/media-node FROM base AS builder COPY package.json pnpm-workspace.yaml ./ diff --git a/node/media-node/cmd/media-node/main.go b/node/media-node/cmd/media-node/main.go index 3d7b9ad..baa4a22 100644 --- a/node/media-node/cmd/media-node/main.go +++ b/node/media-node/cmd/media-node/main.go @@ -24,7 +24,7 @@ import ( "github.com/sthmedia/media-node/internal/streaming" ) -var version = "1.2.2" +var version = "1.2.3" func main() { if len(os.Args) < 2 { @@ -125,6 +125,9 @@ func runService() { }) ctrlClient = control.NewClient(cfg, creds, store, sc, streamer, version, configPath) + sc.SetOnScanComplete(func() { + ctrlClient.RequestFullLibrarySync() + }) streamer.SetSessionEndHandler(func(sessionID, reason string) { ctrlClient.NotifySessionEnded(sessionID, reason) }) diff --git a/node/media-node/internal/control/client.go b/node/media-node/internal/control/client.go index e10b666..8e73fb3 100644 --- a/node/media-node/internal/control/client.go +++ b/node/media-node/internal/control/client.go @@ -594,6 +594,10 @@ func (c *Client) flushLibraryEvents(events []scanner.LibraryEvent) { } } +func (c *Client) RequestFullLibrarySync() { + go c.sendFullSync() +} + func (c *Client) sendFullSync() { c.mu.Lock() if c.fullSyncActive { diff --git a/node/media-node/internal/scanner/scanner.go b/node/media-node/internal/scanner/scanner.go index 9fab0f3..825e787 100644 --- a/node/media-node/internal/scanner/scanner.go +++ b/node/media-node/internal/scanner/scanner.go @@ -42,15 +42,16 @@ type LibraryEvent struct { type EventHandler func(events []LibraryEvent) type Scanner struct { - cfg *config.Config - store *database.Store - onEvents EventHandler - mu sync.Mutex - scanning bool - rescanQueued bool - status string - resetSchedule chan struct{} - reloadWatch chan struct{} + cfg *config.Config + store *database.Store + onEvents EventHandler + onScanComplete func() + mu sync.Mutex + scanning bool + rescanQueued bool + status string + resetSchedule chan struct{} + reloadWatch chan struct{} } func New(cfg *config.Config, store *database.Store, onEvents EventHandler) *Scanner { @@ -64,6 +65,13 @@ func New(cfg *config.Config, store *database.Store, onEvents EventHandler) *Scan } } +// SetOnScanComplete registers a hook after a full scan finishes (no pending re-run). +func (s *Scanner) SetOnScanComplete(fn func()) { + s.mu.Lock() + s.onScanComplete = fn + s.mu.Unlock() +} + func (s *Scanner) Status() string { s.mu.Lock() defer s.mu.Unlock() @@ -231,15 +239,36 @@ func (s *Scanner) FullScan() { roots := s.cfg.Media.EffectiveRoots() s.mu.Unlock() - log.Println("Starting full media scan...") + log.Printf("Starting full media scan (%d roots)...", len(roots)) found := make(map[string]bool) + aborted := false for _, root := range roots { + s.mu.Lock() + if s.rescanQueued { + s.mu.Unlock() + aborted = true + log.Printf("Aborting scan early (roots/config changed); will re-run") + break + } + s.mu.Unlock() + kind := root.Kind if kind == "series" { kind = "episode" } + before := len(found) s.walkRoot(root.Path, kind, root.ShelfID, found) + log.Printf("Scan root %s (%s) shelf=%s → +%d files", root.Path, kind, root.ShelfID, len(found)-before) + } + + if aborted { + s.mu.Lock() + s.scanning = false + s.status = "idle" + s.mu.Unlock() + log.Println("Re-running full scan after path/config change...") + continue } existing, _ := s.store.AllFiles() @@ -261,11 +290,16 @@ func (s *Scanner) FullScan() { s.scanning = false s.status = "idle" again := s.rescanQueued + doneHook := s.onScanComplete s.mu.Unlock() - if !again { - return + if again { + log.Println("Re-running full scan after path/config change...") + continue } - log.Println("Re-running full scan after path/config change...") + if doneHook != nil { + go doneHook() + } + return } }