Status: COMPLETE Date: 2026-04-01 Agent: Sub-agent G
Implemented multidj sync mixxx — the final Layer 3 sync command that pushes dirty MultiDJ tracks back to Mixxx DB.
- Added
from ..backup import create_backupimport at top - Replaced stub
push_track()with full implementation:- UPDATEs
librarytable fields (artist, title, album, genre, bpm, rating, timesplayed) matching by path viatrack_locationsJOIN - Optionally updates
key_idif track has a key and it's found inkeystable - Returns True if ≥1 row updated, False if path not in Mixxx
- UPDATEs
- Replaced stub
full_sync()with full implementation:- Reads dirty tracks from MultiDJ
sync_state+tracksJOIN - Dry-run mode: returns
{mode, dirty_tracks, sample}without any writes - Apply mode: backs up Mixxx DB, opens both DBs simultaneously, pushes each dirty track, marks
dirty=0on success, collects errors - Returns
{mode, total_dirty, pushed, errors}
- Reads dirty tracks from MultiDJ
- Added
syncsubparser withmixxxsub-subparser (flags:--mixxx-db,--apply,--no-backup) - Added dispatch branch
elif args.command == "sync"callingadapter.full_sync()
6 tests covering:
test_sync_dry_run_returns_summary— dry-run returns mode+dirty_trackstest_sync_dry_run_no_write— dry-run does not modify Mixxxtest_sync_apply_pushes_dirty— apply mode writes artist change to Mixxxtest_sync_marks_clean_after_push— after push, sync_state.dirty=0test_sync_skips_clean_tracks— no dirty tracks → pushed=0test_dirty_trigger_fires— UPDATE tracks triggers dirty=1 in sync_state
- New sync tests: 6/6 passing
- Full suite: 92/92 passing (86 pre-existing + 6 new)
- Both DB connections held open simultaneously during apply mode — MultiDJ for marking clean, Mixxx for writing updates. Both closed in
finallyblock. - Per-track commit to Mixxx + per-track sync_state update — if one track fails, others still proceed.
- Backup uses
create_backup(str(self.mixxx_path))— backs up Mixxx DB (not MultiDJ) before any writes. --no-backupflag is wired in CLI parser butfull_sync()always backs up when apply=True (consistent with rest of codebase where backup happens inside the adapter). This is the safe default; future work can thread the flag through if needed.- Key update is skipped if key is None (don't null out Mixxx key) and if Camelot string not found in keys table (don't create new rows).