Document build/test/lint commands, the cmd -> download -> deezer pipeline architecture, and codebase conventions so future Copilot sessions can work effectively in this repository.
88 lines
4.2 KiB
Markdown
88 lines
4.2 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
gofmt -l . # must print nothing; CI fails on unformatted files
|
|
go vet ./...
|
|
go test ./...
|
|
go build ./...
|
|
```
|
|
|
|
Run a single test:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
- **`cmd`** is a thin cobra layer (`download`, `login`, `logout`, `update`,
|
|
`version`). It parses flags and delegates; it does not contain download
|
|
logic. `cmd.Execute` also 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/deezer`** is 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, and `Resource` implementation
|
|
(`Album`/`Playlist`/`Artist`/`Single`). Adding a new resource kind means
|
|
extending every switch in `kind.go`. `Resource`'s `decode` method is
|
|
unexported to seal the interface to this package.
|
|
- **`internal/download`** (package `download`) drives the per-track pipeline
|
|
via `Downloader.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;
|
|
only `context.Canceled` stops the loop early.
|
|
- **`internal/store`** is 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/tag`** writes 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/config`** has no config file — the output directory
|
|
(`~/Music/GoDeez`) is fixed, and the only setting is the `DEEZER_ARL`
|
|
env var, which is optional (falls back to keyring-stored credentials from
|
|
`godeez login`).
|
|
- **`internal/buildinfo`** holds the version/commit/date injected by
|
|
goreleaser via `-ldflags`; unreleased builds report `dev`, which disables
|
|
update-related behavior (`IsDev()`).
|
|
- **`internal/update`** implements self-update: checks GitHub releases,
|
|
verifies the downloaded binary's SHA256 against the published
|
|
`checksums.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.
|
|
- `Kind` in `internal/deezer/kind.go` is 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_ARL` always takes precedence over keyring-stored
|
|
login credentials when both are present.
|