v0.9.1: playlist merges stop losing your track order
What scrambled `a nissa one` took three defects at once. Reorder it on machine A; open it on machine B and resize the window; B's file is now newer, so the merge takes B's order — the old one — wholesale. _merge_playlist was a 2-way union with no common ancestor: one side's order wholesale, the other side's extras appended at the tail, so a track inserted in the middle on one machine arrived at the bottom on the other. merge_track_order re-inserts each side-only track after the nearest track both copies share instead. _reconcile_playlist (the live-reload path) uses the same helper. The union invariant is unchanged and property-tested over 300 random pairs — a merge never drops a track. Whose order wins is now Playlist.date_modified, bumped only in _set_track_ids (add / remove / reorder / undo) and deliberately not by mark_playlist_settings_dirty, with a file-mtime fallback for playlists written before the field existed. The file's mtime was a lie: the last column is stretch-sized, so Qt re-fires sectionResized whenever the viewport width changes, and a window resize or splitter drag rewrote the open playlist's JSON — moving its mtime and handing Syncthing another conflict — for a width apply_settings overrides on load anyway. _on_section_resized now skips that section; genuine drags on every other column still persist. Replayed on a copy of the real playlists: `a nissa one` keeps its reorder against a newer-by-mtime opponent, and `a nissa ideas` takes the other machine's two mid-list inserts at 5 and 12 rather than 26 and 27. Claude-Session: https://claude.ai/code/session_01M8Mzze7shr5pZoEgKQU5NW
This commit is contained in:
@@ -313,6 +313,11 @@ class LibraryManager(QObject):
|
||||
if playlist is None:
|
||||
return
|
||||
playlist.track_ids = list(ids)
|
||||
# The single funnel for every content change (add / remove / reorder /
|
||||
# undo), and deliberately the *only* place date_modified moves — a
|
||||
# column resize must not look like an edit to the merge. See
|
||||
# conflict_resolver._playlist_newer.
|
||||
playlist.date_modified = _utc_now_iso()
|
||||
self._mark_playlist(pid)
|
||||
self.playlist_content_changed.emit(pid)
|
||||
|
||||
@@ -974,13 +979,13 @@ def _reconcile_track(mem_track, disk_track):
|
||||
|
||||
|
||||
def _reconcile_playlist(mem_pl, disk_pl):
|
||||
"""Reconcile a playlist we were locally editing with the disk copy: keep our
|
||||
order and append any tracks that exist only on the other machine (a merge
|
||||
"""Reconcile a playlist we were locally editing with the disk copy: our order
|
||||
wins (the local edit is the more recent one), and tracks that exist only on
|
||||
the other machine are merged back into position rather than appended (a merge
|
||||
never drops tracks)."""
|
||||
local = list(mem_pl.track_ids)
|
||||
local_set = set(local)
|
||||
mem_pl.track_ids = local + [tid for tid in disk_pl.track_ids
|
||||
if tid not in local_set]
|
||||
from lintunes.storage.conflict_resolver import merge_track_order
|
||||
mem_pl.track_ids = merge_track_order(list(mem_pl.track_ids),
|
||||
list(disk_pl.track_ids))
|
||||
|
||||
|
||||
def _utc_now_iso() -> str:
|
||||
|
||||
Reference in New Issue
Block a user