Plugin: sync rework (named/full sync, membership replacement, no playlist mutation) #30

Closed
opened 2026-08-19 18:32:55 +00:00 by hermes · 1 comment
Owner

Context

SPEC.md §5 sync. Current sync.py does nearly everything the SPEC forbids: creates and renames local playlists, uses the uuid5 + "Apple Music playlist" description sentinel mapping, --prune deletes playlists, syncs whatever the gateway returns rather than configured mappings, mutates while fetching, and has no dry-run.

Task

  • sync [LOCAL_PLAYLIST_NAME]: no argument processes every configured mapping; an argument processes the mapping whose target local name matches; an unconfigured name is an error.
  • Apple is authoritative for contents and order: preserve exact order and duplicate occurrences; omit unmatched tracks from the local playlist and report them prominently as missing — missing matches never fail sync; leave local playlist metadata (descriptions etc.) unchanged.
  • Never create or rename playlists — drop the uuid5 mapping and description sentinel entirely; the configured local target must already exist.
  • Persist to plugin state: upsert fetched tracks rows, replace that playlist's playlist_memberships rows.
  • Update playlistmanager contents via its public store API for existing playlists only.
  • Named single-playlist sync: fetch only that playlist; replace only its membership rows and playlistmanager contents; no global pruning; track records retained indefinitely.
  • No-argument full sync: fetch and validate all configured playlists before any mutation; replace the complete membership set per playlist; may prune stale membership rows only with a complete remote+local view; never delete tracks rows, beets metadata, or playlists; no pruning if any view is incomplete.
  • A configured Apple playlist that no longer exists remotely is an error.
  • Remove the --prune playlist-deletion flag (superseded by membership pruning rules).
  • Fetch/validate-before-mutate ordering; idempotent re-runs; every failure reported with non-zero overall exit on partial completion.

Acceptance criteria

  • Named and full sync behave per SPEC; unconfigured name errors.
  • No playlist create/rename/delete anywhere in the sync path.
  • Order and duplicates preserved; missing tracks reported, non-fatal.
  • Membership replacement + conditional pruning semantics tested (incl. incomplete-view no-prune).
  • Missing remote playlist is a distinguishable error.

Ref: SPEC.md §5 sync, §11.

## Context SPEC.md §5 `sync`. Current `sync.py` does nearly everything the SPEC forbids: creates and **renames** local playlists, uses the uuid5 + "Apple Music playlist" description sentinel mapping, `--prune` **deletes** playlists, syncs whatever the gateway returns rather than configured mappings, mutates while fetching, and has no dry-run. ## Task - `sync [LOCAL_PLAYLIST_NAME]`: no argument processes every configured mapping; an argument processes the mapping whose **target local name** matches; an unconfigured name is an error. - Apple is authoritative for contents and order: preserve exact order and duplicate occurrences; omit unmatched tracks from the local playlist and report them prominently as missing — missing matches **never fail** sync; leave local playlist metadata (descriptions etc.) unchanged. - **Never create or rename playlists** — drop the uuid5 mapping and description sentinel entirely; the configured local target must already exist. - Persist to plugin state: upsert fetched `tracks` rows, replace that playlist's `playlist_memberships` rows. - Update playlistmanager contents via its public store API for existing playlists only. - Named single-playlist sync: fetch only that playlist; replace only its membership rows and playlistmanager contents; **no global pruning**; track records retained indefinitely. - No-argument full sync: fetch and validate **all** configured playlists before any mutation; replace the complete membership set per playlist; may prune stale membership rows only with a complete remote+local view; never delete `tracks` rows, beets metadata, or playlists; no pruning if any view is incomplete. - A configured Apple playlist that no longer exists remotely is an error. - Remove the `--prune` playlist-deletion flag (superseded by membership pruning rules). - Fetch/validate-before-mutate ordering; idempotent re-runs; every failure reported with non-zero overall exit on partial completion. ## Acceptance criteria - [ ] Named and full sync behave per SPEC; unconfigured name errors. - [ ] No playlist create/rename/delete anywhere in the sync path. - [ ] Order and duplicates preserved; missing tracks reported, non-fatal. - [ ] Membership replacement + conditional pruning semantics tested (incl. incomplete-view no-prune). - [ ] Missing remote playlist is a distinguishable error. Ref: SPEC.md §5 `sync`, §11.
Author
Owner

Delivered in merged PR #36 (cb442af). Issue-specific implementation commits:

  • #22: 4937fad (completed by the SPEC-aligned documentation in 02d5954)
  • #30: 1e245f2
  • #31: 9186be5
  • #32: 0a8ae97
  • #33: 02d5954

Verification: 95 tests passed; focused Ruff checks, compileall, diff checks, isolated-BEETSDIR command smoke testing, and package build all passed. Issue #34 remains open by maintainer request for the deferred real-library acceptance run.

Delivered in merged PR #36 (`cb442af`). Issue-specific implementation commits: - #22: `4937fad` (completed by the SPEC-aligned documentation in `02d5954`) - #30: `1e245f2` - #31: `9186be5` - #32: `0a8ae97` - #33: `02d5954` Verification: 95 tests passed; focused Ruff checks, compileall, diff checks, isolated-`BEETSDIR` command smoke testing, and package build all passed. Issue #34 remains open by maintainer request for the deferred real-library acceptance run.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coop/beets-appleplaylists#30
No description provided.