fix(S21/T01): fix all lint warnings and upgrade wsl to wsl_v5

Files:
- .golangci.yml
- backend/events/cmd/genevents/main.go
- backend/fileutil/atomicwrite_test.go
- backend/library/library.go
- backend/player/buffered_streamer_test.go
- backend/player/player.go
- backend/tagwriter/dbsync.go
- backend/tagwriter/mp3_test.go
- backend/tagwriter/ogg.go
- backend/tagwriter/ogg_test.go
- backend/tagwriter/ogg_vorbis.go
- backend/tagwriter/pipeline.go
- backend/tagwriter/tagwriter.go
- backend/tagwriter/wav_test.go
This commit is contained in:
2026-03-20 14:36:18 -04:00
parent 2409b3435a
commit f16157a213
14 changed files with 61 additions and 91 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ linters:
- usestdlibvars - usestdlibvars
- usetesting - usetesting
- whitespace - whitespace
- wsl - wsl_v5
formatters: formatters:
enable: enable:
- gci - gci
+1
View File
@@ -20,6 +20,7 @@ import (
func main() { func main() {
source := flag.String("source", "events.go", "path to Go events source file") source := flag.String("source", "events.go", "path to Go events source file")
output := flag.String("output", "", "path to TypeScript output file (stdout if empty)") output := flag.String("output", "", "path to TypeScript output file (stdout if empty)")
flag.Parse() flag.Parse()
consts, err := parseEvents(*source) consts, err := parseEvents(*source)
+1
View File
@@ -261,6 +261,7 @@ func TestAtomicWrite_SyncAndClose(t *testing.T) {
// Write 1 MiB of data. // Write 1 MiB of data.
const size = 1 << 20 //nolint:mnd const size = 1 << 20 //nolint:mnd
data := bytes.Repeat([]byte("x"), size) data := bytes.Repeat([]byte("x"), size)
err := AtomicWrite(slog.Default(), target, func(tmp *os.File) error { err := AtomicWrite(slog.Default(), target, func(tmp *os.File) error {
-1
View File
@@ -420,7 +420,6 @@ func (l *Library) scanInternal(
return nil return nil
}, },
) )
if walkErr != nil { if walkErr != nil {
metrics.addWarning( metrics.addWarning(
"", "walk", "", "walk",
+3
View File
@@ -54,6 +54,7 @@ func finiteStreamer(n int) beep.Streamer {
func TestBufferedStreamer_BasicStream(t *testing.T) { func TestBufferedStreamer_BasicStream(t *testing.T) {
const total = 1000 const total = 1000
src := finiteStreamer(total) src := finiteStreamer(total)
bs := NewBufferedStreamer(src, 2048) bs := NewBufferedStreamer(src, 2048)
@@ -120,6 +121,7 @@ func TestBufferedStreamer_BasicStream(t *testing.T) {
func TestBufferedStreamer_SmallReads(t *testing.T) { func TestBufferedStreamer_SmallReads(t *testing.T) {
const total = 200 const total = 200
src := finiteStreamer(total) src := finiteStreamer(total)
bs := NewBufferedStreamer(src, 512) bs := NewBufferedStreamer(src, 512)
@@ -185,6 +187,7 @@ func TestBufferedStreamer_SmallReads(t *testing.T) {
func TestBufferedStreamer_SourceDrained(t *testing.T) { func TestBufferedStreamer_SourceDrained(t *testing.T) {
const total = 100 const total = 100
src := finiteStreamer(total) src := finiteStreamer(total)
bs := NewBufferedStreamer(src, 256) bs := NewBufferedStreamer(src, 256)
-1
View File
@@ -802,7 +802,6 @@ func (p *Player) seekLocked(targetSeconds int) error {
return p.seeker.Seek(samples) return p.seeker.Seek(samples)
}() }()
if seekErr != nil { if seekErr != nil {
speaker.Unlock() speaker.Unlock()
+2
View File
@@ -514,6 +514,7 @@ func generateSizedVariants(logger *slog.Logger, imgData []byte, dir, hashStr str
src, _, err := image.Decode(bytes.NewReader(imgData)) src, _, err := image.Decode(bytes.NewReader(imgData))
if err != nil { if err != nil {
logger.Warn("could not decode image for thumbnails", "err", err) logger.Warn("could not decode image for thumbnails", "err", err)
return return
} }
@@ -537,6 +538,7 @@ func generateSizedVariants(logger *slog.Logger, imgData []byte, dir, hashStr str
var buf bytes.Buffer var buf bytes.Buffer
if encErr := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: tier.quality}); encErr != nil { if encErr := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: tier.quality}); encErr != nil {
logger.Warn("could not encode thumbnail", "tier", tier.suffix, "err", encErr) logger.Warn("could not encode thumbnail", "tier", tier.suffix, "err", encErr)
continue continue
} }
+1 -1
View File
@@ -227,10 +227,10 @@ func TestWriteMp3Tags_AtomicSafety(t *testing.T) {
// Attempt to write to a non-existent directory to force AtomicWrite // Attempt to write to a non-existent directory to force AtomicWrite
// to fail (the temp file creation will fail). // to fail (the temp file creation will fail).
badPath := filepath.Join(dir, "nonexistent", "subdir", "file.mp3") badPath := filepath.Join(dir, "nonexistent", "subdir", "file.mp3")
writeErr := writeMp3Tags(testLogger(), badPath, TagChanges{ writeErr := writeMp3Tags(testLogger(), badPath, TagChanges{
FieldTitle: "After", FieldTitle: "After",
}) })
if writeErr == nil { if writeErr == nil {
t.Fatal("expected error for non-existent path, got nil") t.Fatal("expected error for non-existent path, got nil")
} }
+24 -75
View File
@@ -14,14 +14,14 @@ import (
// Sentinel errors for OGG operations. // Sentinel errors for OGG operations.
var ( var (
errNotOgg = errors.New("not an OGG file") errNotOgg = errors.New("not an OGG file")
errNotVorbis = errors.New("not an OGG Vorbis file") errNotVorbis = errors.New("not an OGG Vorbis file")
errOggMultiStream = errors.New("this OGG file contains multiple streams and cannot be edited") errOggMultiStream = errors.New("this OGG file contains multiple streams and cannot be edited")
errOggTruncated = errors.New("OGG file is truncated") errOggTruncated = errors.New("OGG file is truncated")
errOggInvalidPage = errors.New("invalid OGG page") errOggBadVersion = errors.New("unsupported OGG version")
errOggBadVersion = errors.New("unsupported OGG version") errOggTooFewPackets = errors.New("ogg vorbis: expected at least 3 header packets")
errOggNoPages = errors.New("OGG file contains no pages") errOggNoPages = errors.New("OGG file contains no pages")
errOggMissingSetup = errors.New("OGG file is missing the setup header packet") errOggMissingSetup = errors.New("OGG file is missing the setup header packet")
) )
// oggCRCTable is the pre-computed 256-entry CRC32 lookup table for OGG. // oggCRCTable is the pre-computed 256-entry CRC32 lookup table for OGG.
@@ -117,7 +117,11 @@ func parseOggPages(logger *slog.Logger, filePath string) ([]oggPage, error) {
segEnd := segStart + numSegments segEnd := segStart + numSegments
if segEnd > len(raw) { if segEnd > len(raw) {
return nil, fmt.Errorf("%w: segment table truncated at offset %d", errOggTruncated, offset) return nil, fmt.Errorf(
"%w: segment table truncated at offset %d",
errOggTruncated,
offset,
)
} }
segmentTable := make([]byte, numSegments) segmentTable := make([]byte, numSegments)
@@ -192,7 +196,8 @@ func parseOggPages(logger *slog.Logger, filePath string) ([]oggPage, error) {
// Validate: first page is Vorbis identification header. // Validate: first page is Vorbis identification header.
firstPacketData := pages[0].data firstPacketData := pages[0].data
if len(firstPacketData) < 7 || firstPacketData[0] != 0x01 || //nolint:mnd // Vorbis ID header magic if len(firstPacketData) < 7 ||
firstPacketData[0] != 0x01 || //nolint:mnd // Vorbis ID header magic
string(firstPacketData[1:7]) != "vorbis" { string(firstPacketData[1:7]) != "vorbis" {
return nil, errNotVorbis return nil, errNotVorbis
} }
@@ -249,6 +254,7 @@ func writeOggPage(w io.Writer, page oggPage) error {
// a value <255 terminates it. // a value <255 terminates it.
func extractPackets(pages []oggPage) [][]byte { func extractPackets(pages []oggPage) [][]byte {
var packets [][]byte var packets [][]byte
var current []byte var current []byte
for _, page := range pages { for _, page := range pages {
@@ -299,7 +305,12 @@ func splitPacketIntoSegments(packet []byte) []byte {
// respecting the 255-segment-per-page limit. The first page gets the // respecting the 255-segment-per-page limit. The first page gets the
// headerType as-is; continuation pages get the continued flag (0x01) // headerType as-is; continuation pages get the continued flag (0x01)
// ORed in. All pages share the same serialNo and granulePos. // ORed in. All pages share the same serialNo and granulePos.
func buildPagesFromSegments(segments []byte, packetData []byte, serialNo uint32, granulePos int64) []oggPage { func buildPagesFromSegments(
segments []byte,
packetData []byte,
serialNo uint32,
granulePos int64,
) []oggPage {
const maxSegmentsPerPage = 255 const maxSegmentsPerPage = 255
var pages []oggPage var pages []oggPage
@@ -400,7 +411,7 @@ func writeOggTags(logger *slog.Logger, filePath string, changes TagChanges) erro
const minHeaderPackets = 3 const minHeaderPackets = 3
if len(packets) < minHeaderPackets { if len(packets) < minHeaderPackets {
return fmt.Errorf("ogg vorbis: expected at least 3 header packets, got %d", len(packets)) return fmt.Errorf("%w: got %d", errOggTooFewPackets, len(packets))
} }
_ = packets[0] // identification packet — preserved as page 0 unchanged _ = packets[0] // identification packet — preserved as page 0 unchanged
@@ -503,72 +514,10 @@ func findAudioPageStart(pages []oggPage) int {
return len(pages) return len(pages)
} }
// serializePacketsToPages is a helper that builds pages from multiple
// packets concatenated into the same page stream. This is used for
// the comment + setup header packets which share pages per Vorbis spec.
func serializePacketsToPages(packets [][]byte, serialNo uint32, granulePos int64) []oggPage {
var allSegs []byte
var allData []byte
for _, pkt := range packets {
segs := splitPacketIntoSegments(pkt)
allSegs = append(allSegs, segs...)
allData = append(allData, pkt...)
}
return buildPagesFromSegments(allSegs, allData, serialNo, granulePos)
}
// computePacketBoundaryInPages determines how many complete packets
// exist starting from the beginning of a sequence of pages, using
// lacing values. It returns the number of complete packets and
// whether the reading ended mid-packet (a continued packet).
func computePacketBoundaryInPages(pages []oggPage) (completedPackets int, midPacket bool) {
inPacket := false
for _, page := range pages {
for _, lacing := range page.segmentTable {
if lacing > 0 {
inPacket = true
}
if lacing < 255 { //nolint:mnd // OGG lacing terminates packet
if inPacket || lacing == 0 {
completedPackets++
inPacket = false
}
}
}
}
return completedPackets, inPacket
}
// oggPagesCopy creates a deep copy of a slice of oggPage.
func oggPagesCopy(pages []oggPage) []oggPage {
cp := make([]oggPage, len(pages))
for i, p := range pages {
cp[i] = oggPage{
headerType: p.headerType,
granulePos: p.granulePos,
serialNo: p.serialNo,
seqNo: p.seqNo,
}
cp[i].segmentTable = make([]byte, len(p.segmentTable))
copy(cp[i].segmentTable, p.segmentTable)
cp[i].data = make([]byte, len(p.data))
copy(cp[i].data, p.data)
}
return cp
}
// pageBytes serializes an OGG page to raw bytes (for CRC verification etc.). // pageBytes serializes an OGG page to raw bytes (for CRC verification etc.).
func pageBytes(page oggPage) []byte { func pageBytes(page oggPage) []byte {
var buf bytes.Buffer var buf bytes.Buffer
_ = writeOggPage(&buf, page) _ = writeOggPage(&buf, page)
return buf.Bytes() return buf.Bytes()
+3 -4
View File
@@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"yellowjacket/backend/metadata" "yellowjacket/backend/metadata"
@@ -601,6 +602,7 @@ func TestWriteOggTags_RejectMultiStream(t *testing.T) {
identSegs := splitPacketIntoSegments(identPacket) identSegs := splitPacketIntoSegments(identPacket)
const serial1 uint32 = 0x11111111 const serial1 uint32 = 0x11111111
const serial2 uint32 = 0x22222222 const serial2 uint32 = 0x22222222
page1 := oggPage{ page1 := oggPage{
@@ -652,8 +654,5 @@ func errorContains(err error, substr string) bool {
return false return false
} }
return bytes.Contains( return strings.Contains(err.Error(), substr)
[]byte(err.Error()),
[]byte(substr),
)
} }
+18 -4
View File
@@ -4,11 +4,18 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
) )
// Sentinel errors for Vorbis Comment operations.
var (
errVorbisPacketShort = errors.New("vorbis comment packet too short")
errVorbisInvalidMagic = errors.New("invalid vorbis comment header magic")
)
// oggVorbisComment holds a parsed Vorbis Comment structure with raw // oggVorbisComment holds a parsed Vorbis Comment structure with raw
// byte preservation for non-edited fields (even if they contain // byte preservation for non-edited fields (even if they contain
// invalid UTF-8). // invalid UTF-8).
@@ -24,11 +31,12 @@ func parseVorbisCommentPacket(packet []byte) (*oggVorbisComment, error) {
const prefixLen = 7 // \x03 + "vorbis" const prefixLen = 7 // \x03 + "vorbis"
if len(packet) < prefixLen { if len(packet) < prefixLen {
return nil, fmt.Errorf("vorbis comment packet too short: %d bytes", len(packet)) return nil, fmt.Errorf("%w: %d bytes", errVorbisPacketShort, len(packet))
} }
if packet[0] != 0x03 || string(packet[1:7]) != "vorbis" { //nolint:mnd // Vorbis comment header magic if packet[0] != 0x03 ||
return nil, fmt.Errorf("invalid vorbis comment header magic") string(packet[1:7]) != "vorbis" { //nolint:mnd // Vorbis comment header magic
return nil, errVorbisInvalidMagic
} }
r := bytes.NewReader(packet[prefixLen:]) r := bytes.NewReader(packet[prefixLen:])
@@ -200,7 +208,13 @@ func buildMetadataBlockPicture(imageData []byte) []byte {
// Calculate size: type(4) + mimeLen(4) + mime + descLen(4) + desc // Calculate size: type(4) + mimeLen(4) + mime + descLen(4) + desc
// + width(4) + height(4) + depth(4) + colors(4) + dataLen(4) + data // + width(4) + height(4) + depth(4) + colors(4) + dataLen(4) + data
size := 4 + 4 + len(mime) + 4 + len(desc) + 4*4 + 4 + len(imageData) //nolint:mnd // FLAC PICTURE block fields size := 4 + 4 + len(
mime,
) + 4 + len(
desc,
) + 4*4 + 4 + len(
imageData,
) //nolint:mnd // FLAC PICTURE block fields
buf := make([]byte, 0, size) buf := make([]byte, 0, size)
+4 -2
View File
@@ -244,11 +244,13 @@ func (tw *TagWriter) BatchWriteTrackTags(
// Set up cancellation channel. // Set up cancellation channel.
tw.cancelBatch = make(chan struct{}) tw.cancelBatch = make(chan struct{})
defer func() { tw.cancelBatch = nil }() defer func() { tw.cancelBatch = nil }()
// Suppress per-track TrackMetadataChanged events — we emit one // Suppress per-track TrackMetadataChanged events — we emit one
// at the end instead. // at the end instead.
tw.suppressEvents = true tw.suppressEvents = true
defer func() { tw.suppressEvents = false }() defer func() { tw.suppressEvents = false }()
for i, filePath := range filePaths { for i, filePath := range filePaths {
@@ -256,12 +258,11 @@ func (tw *TagWriter) BatchWriteTrackTags(
select { select {
case <-tw.cancelBatch: case <-tw.cancelBatch:
result.Cancelled = true result.Cancelled = true
tw.logger.Info("batch write cancelled", tw.logger.Info("batch write cancelled",
"at", i, "at", i,
"total", total, "total", total,
) )
break
default: default:
} }
@@ -276,6 +277,7 @@ func (tw *TagWriter) BatchWriteTrackTags(
FilePath: filePath, FilePath: filePath,
Error: err.Error(), Error: err.Error(),
}) })
tw.logger.Warn("batch track failed", tw.logger.Warn("batch track failed",
"path", filePath, "path", filePath,
"err", err, "err", err,
+1
View File
@@ -99,6 +99,7 @@ func asBytes(v any) ([]byte, bool) {
} }
out := make([]byte, len(arr)) out := make([]byte, len(arr))
for i, elem := range arr { for i, elem := range arr {
f, ok := elem.(float64) f, ok := elem.(float64)
if !ok { if !ok {
+2 -2
View File
@@ -32,6 +32,7 @@ func createTestWAV(
// --- data chunk: 200 bytes of silence --- // --- data chunk: 200 bytes of silence ---
const silenceLen = 200 //nolint:mnd const silenceLen = 200 //nolint:mnd
silence := make([]byte, silenceLen) silence := make([]byte, silenceLen)
// --- optional id3 chunk --- // --- optional id3 chunk ---
@@ -401,7 +402,6 @@ func TestWriteWavTags_AtomicSafety(t *testing.T) {
writeErr := writeWavTags(testLogger(), badPath, TagChanges{ writeErr := writeWavTags(testLogger(), badPath, TagChanges{
FieldTitle: "After", FieldTitle: "After",
}) })
if writeErr == nil { if writeErr == nil {
t.Fatal("expected error for non-existent path, got nil") t.Fatal("expected error for non-existent path, got nil")
} }
@@ -443,7 +443,6 @@ func TestWriteWavTags_RejectsRF64(t *testing.T) {
err := writeWavTags(testLogger(), path, TagChanges{ err := writeWavTags(testLogger(), path, TagChanges{
FieldTitle: "Should Fail", FieldTitle: "Should Fail",
}) })
if err == nil { if err == nil {
t.Fatal("expected error for RF64 file, got nil") t.Fatal("expected error for RF64 file, got nil")
} }
@@ -601,6 +600,7 @@ func createTestWAVWithExtraChunks(
// 200 bytes of silence for data chunk. // 200 bytes of silence for data chunk.
const silenceLen = 200 //nolint:mnd const silenceLen = 200 //nolint:mnd
silence := make([]byte, silenceLen) silence := make([]byte, silenceLen)
// Calculate RIFF payload. // Calculate RIFF payload.