diff --git a/deploy/docker/Dockerfile.master-api b/deploy/docker/Dockerfile.master-api index b03b840..ae5c36f 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.5" -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.5" -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.6" -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.6" -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 85e85e0..587c419 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.5" +var version = "1.2.6" func main() { if len(os.Args) < 2 { diff --git a/node/media-node/internal/control/client.go b/node/media-node/internal/control/client.go index b78f22b..9c919bd 100644 --- a/node/media-node/internal/control/client.go +++ b/node/media-node/internal/control/client.go @@ -283,12 +283,18 @@ func (c *Client) handleMessage(msgType string, payload json.RawMessage) { } log.Println("Connected to Master") c.applyScannerSettings(ack.FullScanInterval, ack.FullScanAt, ack.FullScanIntervalSeconds) + rootsChanged := false if len(ack.ScanRoots) > 0 { - c.applyScanRoots(ack.ScanRoots) + rootsChanged = c.applyScanRoots(ack.ScanRoots) } else if len(ack.MoviesPaths) > 0 || len(ack.SeriesPaths) > 0 { c.applyMediaPaths(ack.MoviesPaths, ack.SeriesPaths) + rootsChanged = true + } + // If roots changed, FullScan already started and will sync when done. + // Otherwise push current inventory (with shelfIds) to Master. + if !rootsChanged { + go c.sendFullSync() } - go c.sendFullSync() case "FULL_LIBRARY_SYNC_ACK": var p struct { Accepted bool `json:"accepted"` @@ -418,7 +424,7 @@ func (c *Client) applyScanRoots(rows []struct { Path string `json:"path"` ShelfID string `json:"shelfId"` Kind string `json:"kind"` -}) { +}) bool { roots := make([]config.MediaRoot, 0, len(rows)) for _, r := range rows { path := strings.TrimSpace(r.Path) @@ -434,7 +440,7 @@ func (c *Client) applyScanRoots(rows []struct { } roots = append(roots, config.MediaRoot{Path: path, ShelfID: r.ShelfID, Kind: kind}) } - c.scanner.UpdateMediaRoots(roots) + changed := c.scanner.UpdateMediaRoots(roots) c.cfg.Media.Roots = roots var movies, series []string for _, r := range roots { @@ -446,11 +452,12 @@ func (c *Client) applyScanRoots(rows []struct { } c.cfg.Media.Movies = movies c.cfg.Media.Series = series - if c.configPath != "" { + if changed && c.configPath != "" { if err := config.Save(c.configPath, c.cfg); err != nil { log.Printf("Failed to save media roots: %v", err) } } + return changed } func normalizePaths(in []string) []string { diff --git a/node/media-node/internal/scanner/scanner.go b/node/media-node/internal/scanner/scanner.go index 6fe1776..8e757ec 100644 --- a/node/media-node/internal/scanner/scanner.go +++ b/node/media-node/internal/scanner/scanner.go @@ -133,8 +133,14 @@ func (s *Scanner) UpdateMediaPaths(movies, series []string) { } // UpdateMediaRoots replaces scan roots (with optional shelf IDs). -func (s *Scanner) UpdateMediaRoots(roots []config.MediaRoot) { +// Returns true when roots changed (and a full scan was started). +func (s *Scanner) UpdateMediaRoots(roots []config.MediaRoot) bool { s.mu.Lock() + if mediaRootsEqual(s.cfg.Media.EffectiveRoots(), roots) { + s.mu.Unlock() + log.Printf("Media roots unchanged (%d roots) — skip rescan", len(roots)) + return false + } s.cfg.Media.Roots = append([]config.MediaRoot{}, roots...) var movies, series []string for _, r := range roots { @@ -151,8 +157,23 @@ func (s *Scanner) UpdateMediaRoots(roots []config.MediaRoot) { case s.reloadWatch <- struct{}{}: default: } - log.Printf("Media roots updated: %d roots", len(roots)) + log.Printf("Media roots updated: %d roots — starting full scan", len(roots)) go s.FullScan() + return true +} + +func mediaRootsEqual(a, b []config.MediaRoot) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if filepath.Clean(a[i].Path) != filepath.Clean(b[i].Path) || + a[i].ShelfID != b[i].ShelfID || + a[i].Kind != b[i].Kind { + return false + } + } + return true } func (s *Scanner) runStartupScan() { @@ -491,7 +512,10 @@ func (s *Scanner) indexFile(path string, info os.FileInfo, eventType, preferredT } localFileID := database.FileID(path) - rev, _ := s.store.NextRevision() + var rev int64 + if emitEvents { + rev, _ = s.store.NextRevision() + } lf := database.LocalFile{ LocalFileID: localFileID,