Make full scans local-only then sync; log RESCAN and per-root progress.

This commit is contained in:
Jos Vooges | STH 2026-08-26 02:46:12 +02:00
parent 49d2f8a186
commit 9168a159f7
4 changed files with 28 additions and 23 deletions

View file

@ -7,8 +7,8 @@ WORKDIR /src
RUN apk add --no-cache git ca-certificates RUN apk add --no-cache git ca-certificates
COPY node/media-node/ ./ COPY node/media-node/ ./
RUN go mod tidy \ 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=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.3" -o /media-node-linux-arm64 ./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 FROM base AS builder
COPY package.json pnpm-workspace.yaml ./ COPY package.json pnpm-workspace.yaml ./

View file

@ -24,7 +24,7 @@ import (
"github.com/sthmedia/media-node/internal/streaming" "github.com/sthmedia/media-node/internal/streaming"
) )
var version = "1.2.3" var version = "1.2.4"
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {

View file

@ -356,6 +356,7 @@ func (c *Client) handleMessage(msgType string, payload json.RawMessage) {
log.Printf("Playback session revoked: %s", p.SessionID) log.Printf("Playback session revoked: %s", p.SessionID)
} }
case "RESCAN": case "RESCAN":
log.Println("RESCAN requested by master")
go c.scanner.FullScan() go c.scanner.FullScan()
case "FULL_LIBRARY_SYNC": case "FULL_LIBRARY_SYNC":
go c.sendFullSync() go c.sendFullSync()

View file

@ -231,6 +231,7 @@ func (s *Scanner) FullScan() {
if s.scanning { if s.scanning {
s.rescanQueued = true s.rescanQueued = true
s.mu.Unlock() s.mu.Unlock()
log.Println("Full scan already running — queued re-run after current pass")
return return
} }
s.scanning = true s.scanning = true
@ -258,8 +259,9 @@ func (s *Scanner) FullScan() {
kind = "episode" kind = "episode"
} }
before := len(found) before := len(found)
s.walkRoot(root.Path, kind, root.ShelfID, found) log.Printf("Scanning root %s (%s) shelf=%s ...", root.Path, kind, root.ShelfID)
log.Printf("Scan root %s (%s) shelf=%s → +%d files", root.Path, kind, root.ShelfID, len(found)-before) 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 { if aborted {
@ -271,31 +273,29 @@ func (s *Scanner) FullScan() {
continue continue
} }
// Local prune only — Master learns removals via the full library sync that follows.
existing, _ := s.store.AllFiles() existing, _ := s.store.AllFiles()
removed := 0
for _, f := range existing { for _, f := range existing {
if !found[f.Path] { if !found[f.Path] {
rev, _ := s.store.NextRevision()
event := LibraryEvent{
Type: "FILE_REMOVED",
NodeRevision: rev,
File: fileToInfo(f),
}
_ = s.store.DeleteFile(f.LocalFileID) _ = 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.mu.Lock()
if s.rescanQueued {
s.scanning = false s.scanning = false
s.status = "idle" s.status = "idle"
again := s.rescanQueued
doneHook := s.onScanComplete
s.mu.Unlock() s.mu.Unlock()
if again {
log.Println("Re-running full scan after path/config change...") log.Println("Re-running full scan after path/config change...")
continue continue
} }
s.scanning = false
s.status = "idle"
doneHook := s.onScanComplete
s.mu.Unlock()
if doneHook != nil { if doneHook != nil {
go doneHook() 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 == "" { if dir == "" {
return return
} }
@ -315,7 +315,7 @@ func (s *Scanner) walkRoot(dir, mediaType, shelfID string, found map[string]bool
return nil return nil
} }
found[path] = true found[path] = true
s.indexFile(path, info, "FILE_UPDATED", mediaType, shelfID) s.indexFile(path, info, "FILE_UPDATED", mediaType, shelfID, emitEvents)
return nil return nil
}) })
} }
@ -425,7 +425,7 @@ func (s *Scanner) runWatchSession() {
info, err := os.Stat(path) info, err := os.Stat(path)
if err == nil && !info.IsDir() && isVideo(path) { if err == nil && !info.IsDir() && isVideo(path) {
kind, shelf := s.detectRootMeta(path, rootType, rootShelf) 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{}) pending = make(map[string]struct{})
@ -474,7 +474,7 @@ func (s *Scanner) detectRootType(path string, rootType map[string]string) string
return detectMediaType(path) 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 mediaType := preferredType
if mediaType == "" { if mediaType == "" {
mediaType = detectMediaType(path) mediaType = detectMediaType(path)
@ -504,6 +504,10 @@ func (s *Scanner) indexFile(path string, info os.FileInfo, eventType, preferredT
} }
_ = s.store.UpsertFile(lf) _ = s.store.UpsertFile(lf)
if !emitEvents {
return
}
info2 := buildMediaInfo(lf) info2 := buildMediaInfo(lf)
info2.ShelfID = shelfID info2.ShelfID = shelfID
event := LibraryEvent{ event := LibraryEvent{