fix(tracks): rewrite RemoveTrack — direct os.Remove + optional Lidarr unmonitor
Replaces commit 50a231f's wrong-shape Lidarr-routed delete. The previous
design called lidarrquarantine.DeleteViaLidarr for Lidarr-managed tracks,
which deletes the entire **album** in Lidarr (Lidarr is album-granular,
no per-track delete API) — silently dropping sibling tracks the operator
didn't ask to remove.
New shape per spec revision 723eee9:
- Always: os.Remove + DB delete + cascade through Minstrel.
- When unmonitor=true AND track has mbid: call new Lidarr.UnmonitorTrack
primitive so Lidarr won't replace. Failure is non-fatal — file is
already gone, DB consistent; the lidarrUnmonitorFailed bool flows to
the wire response so the UI can surface a follow-up "unmonitor
manually" toast.
Service signature changes from `(trackID, adminID) → (delAlbum, delArtist, err)`
to `(trackID, adminID, unmonitor) → (delAlbum, delArtist, lidarrFailed, err)`.
Adds Lidarr.UnmonitorTrack with full three-step API walk:
1. GET /api/v1/album?foreignAlbumId={mbid} → Lidarr's internal album id
2. GET /api/v1/track?albumId={id} → match track by foreignTrackId
3. PUT /api/v1/track/monitor with {trackIds:[id], monitored:false}
Refactors post() into a shared bodyRequest() so put() reuses the same
status-code → typed-error mapping. The album mbid is captured *before*
the cascade-delete transaction — DeleteAlbumIfEmpty may remove the
album row, after which a post-commit GetAlbumByID returns ErrNoRows
and the unmonitor walk would have nothing to walk against.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,12 +83,25 @@ func readBodySnippet(r io.Reader) string {
|
||||
// status codes to typed errors; the caller is responsible for closing the
|
||||
// returned body.
|
||||
func (c *Client) post(ctx context.Context, path string, body []byte) (*http.Response, error) {
|
||||
return c.bodyRequest(ctx, http.MethodPost, path, body)
|
||||
}
|
||||
|
||||
// put is the shared PUT helper. Body must be marshaled JSON. Mirrors post() —
|
||||
// same status-code mapping, same caller-closes-body contract.
|
||||
func (c *Client) put(ctx context.Context, path string, body []byte) (*http.Response, error) {
|
||||
return c.bodyRequest(ctx, http.MethodPut, path, body)
|
||||
}
|
||||
|
||||
// bodyRequest is the shared core for POST and PUT. Lidarr's monitor toggle
|
||||
// is PUT-shaped, so factoring it out keeps the post/put helpers from
|
||||
// duplicating the status-code mapping.
|
||||
func (c *Client) bodyRequest(ctx context.Context, method, path string, body []byte) (*http.Response, error) {
|
||||
u, err := url.Parse(c.BaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrUnreachable, err)
|
||||
}
|
||||
u.Path = strings.TrimRight(u.Path, "/") + path
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
|
||||
req, err := http.NewRequestWithContext(ctx, method, u.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user