diff --git a/deploy/docker/Dockerfile.master-api b/deploy/docker/Dockerfile.master-api index 9d0812d..98a00c3 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.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 + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=1.2.4" -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.4" -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 baa4a22..54409ea 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.3" +var version = "1.2.4" 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 8e73fb3..b78f22b 100644 --- a/node/media-node/internal/control/client.go +++ b/node/media-node/internal/control/client.go @@ -356,6 +356,7 @@ func (c *Client) handleMessage(msgType string, payload json.RawMessage) { log.Printf("Playback session revoked: %s", p.SessionID) } case "RESCAN": + log.Println("RESCAN requested by master") go c.scanner.FullScan() case "FULL_LIBRARY_SYNC": go c.sendFullSync() diff --git a/node/media-node/internal/scanner/scanner.go b/node/media-node/internal/scanner/scanner.go index 825e787..6fe1776 100644 --- a/node/media-node/internal/scanner/scanner.go +++ b/node/media-node/internal/scanner/scanner.go @@ -231,6 +231,7 @@ func (s *Scanner) FullScan() { if s.scanning { s.rescanQueued = true s.mu.Unlock() + log.Println("Full scan already running — queued re-run after current pass") return } s.scanning = true @@ -258,8 +259,9 @@ func (s *Scanner) FullScan() { 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) + log.Printf("Scanning root %s (%s) shelf=%s ...", root.Path, kind, root.ShelfID) + s.walkRoot(root.Path, kind, root.ShelfID, found, false) + log.Printf("Scan root done %s → +%d files (total %d)", root.Path, len(found)-before, len(found)) } if aborted { @@ -271,31 +273,29 @@ func (s *Scanner) FullScan() { continue } + // Local prune only — Master learns removals via the full library sync that follows. existing, _ := s.store.AllFiles() + removed := 0 for _, f := range existing { if !found[f.Path] { - rev, _ := s.store.NextRevision() - event := LibraryEvent{ - Type: "FILE_REMOVED", - NodeRevision: rev, - File: fileToInfo(f), - } _ = s.store.DeleteFile(f.LocalFileID) - s.onEvents([]LibraryEvent{event}) + removed++ } } - log.Printf("Full scan completed (%d files)", len(found)) + log.Printf("Full scan completed (%d files, %d removed locally)", len(found), removed) s.mu.Lock() - s.scanning = false - s.status = "idle" - again := s.rescanQueued - doneHook := s.onScanComplete - s.mu.Unlock() - if again { + if s.rescanQueued { + s.scanning = false + s.status = "idle" + s.mu.Unlock() log.Println("Re-running full scan after path/config change...") continue } + s.scanning = false + s.status = "idle" + doneHook := s.onScanComplete + s.mu.Unlock() if doneHook != nil { go doneHook() } @@ -303,7 +303,7 @@ func (s *Scanner) FullScan() { } } -func (s *Scanner) walkRoot(dir, mediaType, shelfID string, found map[string]bool) { +func (s *Scanner) walkRoot(dir, mediaType, shelfID string, found map[string]bool, emitEvents bool) { if dir == "" { return } @@ -315,7 +315,7 @@ func (s *Scanner) walkRoot(dir, mediaType, shelfID string, found map[string]bool return nil } found[path] = true - s.indexFile(path, info, "FILE_UPDATED", mediaType, shelfID) + s.indexFile(path, info, "FILE_UPDATED", mediaType, shelfID, emitEvents) return nil }) } @@ -425,7 +425,7 @@ func (s *Scanner) runWatchSession() { info, err := os.Stat(path) if err == nil && !info.IsDir() && isVideo(path) { kind, shelf := s.detectRootMeta(path, rootType, rootShelf) - s.indexFile(path, info, "FILE_ADDED", kind, shelf) + s.indexFile(path, info, "FILE_ADDED", kind, shelf, true) } } pending = make(map[string]struct{}) @@ -474,7 +474,7 @@ func (s *Scanner) detectRootType(path string, rootType map[string]string) string return detectMediaType(path) } -func (s *Scanner) indexFile(path string, info os.FileInfo, eventType, preferredType, shelfID string) { +func (s *Scanner) indexFile(path string, info os.FileInfo, eventType, preferredType, shelfID string, emitEvents bool) { mediaType := preferredType if mediaType == "" { mediaType = detectMediaType(path) @@ -504,6 +504,10 @@ func (s *Scanner) indexFile(path string, info os.FileInfo, eventType, preferredT } _ = s.store.UpsertFile(lf) + if !emitEvents { + return + } + info2 := buildMediaInfo(lf) info2.ShelfID = shelfID event := LibraryEvent{