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:
+1
-1
@@ -28,7 +28,7 @@ linters:
|
||||
- usestdlibvars
|
||||
- usetesting
|
||||
- whitespace
|
||||
- wsl
|
||||
- wsl_v5
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
func main() {
|
||||
source := flag.String("source", "events.go", "path to Go events source file")
|
||||
output := flag.String("output", "", "path to TypeScript output file (stdout if empty)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
consts, err := parseEvents(*source)
|
||||
|
||||
@@ -261,6 +261,7 @@ func TestAtomicWrite_SyncAndClose(t *testing.T) {
|
||||
|
||||
// Write 1 MiB of data.
|
||||
const size = 1 << 20 //nolint:mnd
|
||||
|
||||
data := bytes.Repeat([]byte("x"), size)
|
||||
|
||||
err := AtomicWrite(slog.Default(), target, func(tmp *os.File) error {
|
||||
|
||||
@@ -420,7 +420,6 @@ func (l *Library) scanInternal(
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if walkErr != nil {
|
||||
metrics.addWarning(
|
||||
"", "walk",
|
||||
|
||||
@@ -54,6 +54,7 @@ func finiteStreamer(n int) beep.Streamer {
|
||||
|
||||
func TestBufferedStreamer_BasicStream(t *testing.T) {
|
||||
const total = 1000
|
||||
|
||||
src := finiteStreamer(total)
|
||||
bs := NewBufferedStreamer(src, 2048)
|
||||
|
||||
@@ -120,6 +121,7 @@ func TestBufferedStreamer_BasicStream(t *testing.T) {
|
||||
|
||||
func TestBufferedStreamer_SmallReads(t *testing.T) {
|
||||
const total = 200
|
||||
|
||||
src := finiteStreamer(total)
|
||||
bs := NewBufferedStreamer(src, 512)
|
||||
|
||||
@@ -185,6 +187,7 @@ func TestBufferedStreamer_SmallReads(t *testing.T) {
|
||||
|
||||
func TestBufferedStreamer_SourceDrained(t *testing.T) {
|
||||
const total = 100
|
||||
|
||||
src := finiteStreamer(total)
|
||||
bs := NewBufferedStreamer(src, 256)
|
||||
|
||||
|
||||
@@ -802,7 +802,6 @@ func (p *Player) seekLocked(targetSeconds int) error {
|
||||
|
||||
return p.seeker.Seek(samples)
|
||||
}()
|
||||
|
||||
if seekErr != nil {
|
||||
speaker.Unlock()
|
||||
|
||||
|
||||
@@ -514,6 +514,7 @@ func generateSizedVariants(logger *slog.Logger, imgData []byte, dir, hashStr str
|
||||
src, _, err := image.Decode(bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
logger.Warn("could not decode image for thumbnails", "err", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -537,6 +538,7 @@ func generateSizedVariants(logger *slog.Logger, imgData []byte, dir, hashStr str
|
||||
var buf bytes.Buffer
|
||||
if encErr := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: tier.quality}); encErr != nil {
|
||||
logger.Warn("could not encode thumbnail", "tier", tier.suffix, "err", encErr)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -227,10 +227,10 @@ func TestWriteMp3Tags_AtomicSafety(t *testing.T) {
|
||||
// Attempt to write to a non-existent directory to force AtomicWrite
|
||||
// to fail (the temp file creation will fail).
|
||||
badPath := filepath.Join(dir, "nonexistent", "subdir", "file.mp3")
|
||||
|
||||
writeErr := writeMp3Tags(testLogger(), badPath, TagChanges{
|
||||
FieldTitle: "After",
|
||||
})
|
||||
|
||||
if writeErr == nil {
|
||||
t.Fatal("expected error for non-existent path, got nil")
|
||||
}
|
||||
|
||||
+24
-75
@@ -14,14 +14,14 @@ import (
|
||||
|
||||
// Sentinel errors for OGG operations.
|
||||
var (
|
||||
errNotOgg = errors.New("not an OGG file")
|
||||
errNotVorbis = errors.New("not an OGG Vorbis file")
|
||||
errOggMultiStream = errors.New("this OGG file contains multiple streams and cannot be edited")
|
||||
errOggTruncated = errors.New("OGG file is truncated")
|
||||
errOggInvalidPage = errors.New("invalid OGG page")
|
||||
errOggBadVersion = errors.New("unsupported OGG version")
|
||||
errOggNoPages = errors.New("OGG file contains no pages")
|
||||
errOggMissingSetup = errors.New("OGG file is missing the setup header packet")
|
||||
errNotOgg = errors.New("not an OGG file")
|
||||
errNotVorbis = errors.New("not an OGG Vorbis file")
|
||||
errOggMultiStream = errors.New("this OGG file contains multiple streams and cannot be edited")
|
||||
errOggTruncated = errors.New("OGG file is truncated")
|
||||
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")
|
||||
errOggMissingSetup = errors.New("OGG file is missing the setup header packet")
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
@@ -192,7 +196,8 @@ func parseOggPages(logger *slog.Logger, filePath string) ([]oggPage, error) {
|
||||
|
||||
// Validate: first page is Vorbis identification header.
|
||||
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" {
|
||||
return nil, errNotVorbis
|
||||
}
|
||||
@@ -249,6 +254,7 @@ func writeOggPage(w io.Writer, page oggPage) error {
|
||||
// a value <255 terminates it.
|
||||
func extractPackets(pages []oggPage) [][]byte {
|
||||
var packets [][]byte
|
||||
|
||||
var current []byte
|
||||
|
||||
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
|
||||
// headerType as-is; continuation pages get the continued flag (0x01)
|
||||
// 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
|
||||
|
||||
var pages []oggPage
|
||||
@@ -400,7 +411,7 @@ func writeOggTags(logger *slog.Logger, filePath string, changes TagChanges) erro
|
||||
const minHeaderPackets = 3
|
||||
|
||||
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
|
||||
@@ -503,72 +514,10 @@ func findAudioPageStart(pages []oggPage) int {
|
||||
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.).
|
||||
func pageBytes(page oggPage) []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
_ = writeOggPage(&buf, page)
|
||||
|
||||
return buf.Bytes()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"yellowjacket/backend/metadata"
|
||||
@@ -601,6 +602,7 @@ func TestWriteOggTags_RejectMultiStream(t *testing.T) {
|
||||
identSegs := splitPacketIntoSegments(identPacket)
|
||||
|
||||
const serial1 uint32 = 0x11111111
|
||||
|
||||
const serial2 uint32 = 0x22222222
|
||||
|
||||
page1 := oggPage{
|
||||
@@ -652,8 +654,5 @@ func errorContains(err error, substr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Contains(
|
||||
[]byte(err.Error()),
|
||||
[]byte(substr),
|
||||
)
|
||||
return strings.Contains(err.Error(), substr)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,18 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"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
|
||||
// byte preservation for non-edited fields (even if they contain
|
||||
// invalid UTF-8).
|
||||
@@ -24,11 +31,12 @@ func parseVorbisCommentPacket(packet []byte) (*oggVorbisComment, error) {
|
||||
const prefixLen = 7 // \x03 + "vorbis"
|
||||
|
||||
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
|
||||
return nil, fmt.Errorf("invalid vorbis comment header magic")
|
||||
if packet[0] != 0x03 ||
|
||||
string(packet[1:7]) != "vorbis" { //nolint:mnd // Vorbis comment header magic
|
||||
return nil, errVorbisInvalidMagic
|
||||
}
|
||||
|
||||
r := bytes.NewReader(packet[prefixLen:])
|
||||
@@ -200,7 +208,13 @@ func buildMetadataBlockPicture(imageData []byte) []byte {
|
||||
|
||||
// Calculate size: type(4) + mimeLen(4) + mime + descLen(4) + desc
|
||||
// + 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)
|
||||
|
||||
|
||||
@@ -244,11 +244,13 @@ func (tw *TagWriter) BatchWriteTrackTags(
|
||||
|
||||
// Set up cancellation channel.
|
||||
tw.cancelBatch = make(chan struct{})
|
||||
|
||||
defer func() { tw.cancelBatch = nil }()
|
||||
|
||||
// Suppress per-track TrackMetadataChanged events — we emit one
|
||||
// at the end instead.
|
||||
tw.suppressEvents = true
|
||||
|
||||
defer func() { tw.suppressEvents = false }()
|
||||
|
||||
for i, filePath := range filePaths {
|
||||
@@ -256,12 +258,11 @@ func (tw *TagWriter) BatchWriteTrackTags(
|
||||
select {
|
||||
case <-tw.cancelBatch:
|
||||
result.Cancelled = true
|
||||
|
||||
tw.logger.Info("batch write cancelled",
|
||||
"at", i,
|
||||
"total", total,
|
||||
)
|
||||
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -276,6 +277,7 @@ func (tw *TagWriter) BatchWriteTrackTags(
|
||||
FilePath: filePath,
|
||||
Error: err.Error(),
|
||||
})
|
||||
|
||||
tw.logger.Warn("batch track failed",
|
||||
"path", filePath,
|
||||
"err", err,
|
||||
|
||||
@@ -99,6 +99,7 @@ func asBytes(v any) ([]byte, bool) {
|
||||
}
|
||||
|
||||
out := make([]byte, len(arr))
|
||||
|
||||
for i, elem := range arr {
|
||||
f, ok := elem.(float64)
|
||||
if !ok {
|
||||
|
||||
@@ -32,6 +32,7 @@ func createTestWAV(
|
||||
|
||||
// --- data chunk: 200 bytes of silence ---
|
||||
const silenceLen = 200 //nolint:mnd
|
||||
|
||||
silence := make([]byte, silenceLen)
|
||||
|
||||
// --- optional id3 chunk ---
|
||||
@@ -401,7 +402,6 @@ func TestWriteWavTags_AtomicSafety(t *testing.T) {
|
||||
writeErr := writeWavTags(testLogger(), badPath, TagChanges{
|
||||
FieldTitle: "After",
|
||||
})
|
||||
|
||||
if writeErr == 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{
|
||||
FieldTitle: "Should Fail",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error for RF64 file, got nil")
|
||||
}
|
||||
@@ -601,6 +600,7 @@ func createTestWAVWithExtraChunks(
|
||||
|
||||
// 200 bytes of silence for data chunk.
|
||||
const silenceLen = 200 //nolint:mnd
|
||||
|
||||
silence := make([]byte, silenceLen)
|
||||
|
||||
// Calculate RIFF payload.
|
||||
|
||||
Reference in New Issue
Block a user