Document build/test/lint commands, the cmd -> download -> deezer pipeline architecture, and codebase conventions so future Copilot sessions can work effectively in this repository.
4.2 KiB
4.2 KiB
GoDeez
GoDeez is a Go CLI that downloads music (tracks, albums, playlists, artist top tracks) from Deezer in MP3 or lossless FLAC/WAV, embeds metadata tags, and tracks what has already been downloaded so repeat runs can skip it.
Build, test, lint
Mirror what CI (.github/workflows/ci.yml) runs:
gofmt -l . # must print nothing; CI fails on unformatted files
go vet ./...
go test ./...
go build ./...
Run a single test:
go test ./internal/download/ -run TestHashFile
go test ./internal/download/ -run TestHashFile -v
Release packaging is validated with goreleaser check (config in
.goreleaser.yaml); releases are cut by pushing a v* tag, which requires a
matching section in CHANGELOG.md (.github/scripts/release-notes.sh
extracts it and fails the release otherwise).
Architecture
The flow is cmd → download.Downloader → deezer client → store/tag/audio:
cmdis a thin cobra layer (download,login,logout,update,version). It parses flags and delegates; it does not contain download logic.cmd.Executealso kicks off an async update check (internal/update.StartCheck) before running the command and prints the result after, so the network round trip overlaps with the command instead of adding to startup time.internal/deezeris the API client.Kind(album/playlist/artist/track) is the single source of truth mapping a resource type to its gw-light page method, request id field, andResourceimplementation (Album/Playlist/Artist/Single). Adding a new resource kind means extending every switch inkind.go.Resource'sdecodemethod is unexported to seal the interface to this package.internal/download(packagedownload) drives the per-track pipeline viaDownloader.Run: fetch resource → for each track, resolve a media source → decide skip/hash-dedupe → stream and decrypt → optionally convert FLAC to WAV → write tags → record in the store. Tracks are processed sequentially, one at a time. Most per-track failures (bad cover, failed BPM lookup, etc.) are collected as warnings rather than aborting the whole run; onlycontext.Canceledstops the loop early.internal/storeis a bbolt-backed ledger (.tracks.db) written as a hidden file inside the output directory, so it travels with the music library it describes. Losing it is harmless — the only cost is re-downloading.internal/tagwrites metadata per container format (ID3v2 for MP3, Vorbis comments for FLAC, ID3 chunk + RIFF LIST/INFO for WAV), dispatching on file extension. Taggers write through a temp file so a mid-write failure can't corrupt existing audio.internal/confighas no config file — the output directory (~/Music/GoDeez) is fixed, and the only setting is theDEEZER_ARLenv var, which is optional (falls back to keyring-stored credentials fromgodeez login).internal/buildinfoholds the version/commit/date injected by goreleaser via-ldflags; unreleased builds reportdev, which disables update-related behavior (IsDev()).internal/updateimplements self-update: checks GitHub releases, verifies the downloaded binary's SHA256 against the publishedchecksums.txt, and replaces the running binary in place.
Conventions
- Package- and function-level doc comments consistently explain why, not just what — e.g. why a check happens where it does, or why an error is handled a particular way. Preserve this style when touching existing code or adding new exported functions.
- Most per-track errors during a download are non-fatal and surfaced as
warnings in the run summary; only cancellation (
context.Canceled) should abort the loop. Keep new failure modes inside this pattern unless they are truly unrecoverable for the whole run. Kindininternal/deezer/kind.gois deliberately exhaustive with no default case that silently returns a zero value in the id/method switches — follow the same pattern (explicit cases, empty/error fallback) if extending it.- Credentials:
DEEZER_ARLalways takes precedence over keyring-stored login credentials when both are present.