summaryrefslogtreecommitdiff
path: root/player-server/internal/scanner/scan_writer.go
blob: aa4ad0c04069febda858b855995f6e728da63157 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Package scanner implements media library scanning logic.
package scanner

import (
	"context"
	"fmt"
	"log/slog"
	"path/filepath"
	"sync/atomic"

	"codeberg.org/snonux/player/internal/repository"
)

// scanWriter persists probed media results to the database. It runs in a
// single goroutine to serialise SQLite writes and avoid concurrent-write
// errors. All probing and thumbnail generation happens in probeWorker before
// results arrive here.
type scanWriter struct {
	store  repository.ScannerStore
	logger *slog.Logger
}

// newScanWriter creates a scanWriter backed by the given store.
func newScanWriter(store repository.ScannerStore, logger *slog.Logger) *scanWriter {
	return &scanWriter{store: store, logger: logger}
}

// run reads fileResults from resultChan and inserts each into the store.
// It logs progress every 25 files and accumulates the count in newFiles.
// Exits when resultChan is closed or scanCtx is cancelled.
func (sw *scanWriter) run(
	ctx context.Context,
	scanCtx context.Context,
	resultChan <-chan fileResult,
	setName string,
	setPath string,
	newFiles *int32,
	sendErr func(error),
) {
	for result := range resultChan {
		if scanCtx.Err() != nil {
			continue
		}
		if _, err := sw.store.CreateMedia(ctx, result.media); err != nil {
			sendErr(fmt.Errorf("create media %q: %w", result.path, err))
			continue
		}
		nf := atomic.AddInt32(newFiles, 1)
		// Log progress on the first insert and every 25 thereafter to give
		// operators visibility into long-running scans without flooding logs.
		if nf == 1 || nf%25 == 0 {
			relPath, _ := filepath.Rel(setPath, result.path)
			sw.logger.Info("scanner set progress", "name", setName, "new_media", nf, "latest", filepath.ToSlash(relPath))
		}
	}
}