v0.12.0: the merge report stops reporting things you didn't do
"unplayed", "missed and never skipped" and "top 100 in past year" showed up in the merge window constantly, and nobody had touched their rules. A live smart playlist's membership is derived, but it was being persisted — and recompute_smart_playlist rewrites it (and bumped date_modified) every time a play count moves. Both machines did that against the same file after every song, so Syncthing conflicted on a list the next load throws away and rebuilds anyway. Membership now stays in memory: save_playlist writes track_ids: [] for a live smart playlist, and the recompute passes touch=False so it marks nothing dirty and moves no timestamp. live_update=False and unsupported criteria are unchanged — their track_ids are a snapshot, which is real content. Since _mark_playlist also dirties the metadata, library_metadata.json stops being rewritten every song too. The rest was presentation. Every summary read at the same weight, so someone resizing a column on the other machine popped and raised the same window as a 21-track reconciliation, described as "Library columns/settings taken from the most recently edited copy." Summaries now carry a level — WARNING for a merge that couldn't resolve cleanly or discarded a side, CHANGE for real content, INFO for cosmetic or derived — and the dialog has a Show: selector that filters to one level and above. It opens at the highest level in the batch, so nothing routine steals focus and a blank window can't happen; a later warning always pulls the view back up to it. The wording says what happened instead of how the merge works: a metadata merge names the keys that actually differed, and adopting the other machine's music folder grades CHANGE rather than INFO, because that one is a setting somebody chose. Claude-Session: https://claude.ai/code/session_01Y9ZEFi4qNJ39FMiBtiAxy2
This commit is contained in:
@@ -317,17 +317,23 @@ class LibraryManager(QObject):
|
||||
self._set_track_ids(pid, after)
|
||||
self._push_track_ids("Reorder Playlist", pid, before, after)
|
||||
|
||||
def _set_track_ids(self, pid: str, ids: list[int]):
|
||||
def _set_track_ids(self, pid: str, ids: list[int], touch: bool = True):
|
||||
"""Set a playlist's membership. ``touch=False`` means "this wasn't an
|
||||
edit": no timestamp, no dirty mark, just the signal — used by the smart
|
||||
recompute, whose result isn't persisted."""
|
||||
playlist = self.library.playlists.get(pid)
|
||||
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)
|
||||
# column resize must not look like an edit to the merge. Nor may a smart
|
||||
# recompute: it fires every time a play count moves, and bumping the
|
||||
# timestamp there made "most recently edited" meaningless for smart
|
||||
# playlists. See conflict_resolver._playlist_newer.
|
||||
if touch:
|
||||
playlist.date_modified = _utc_now_iso()
|
||||
self._mark_playlist(pid)
|
||||
self.playlist_content_changed.emit(pid)
|
||||
|
||||
def _push_track_ids(self, label, pid, before, after):
|
||||
@@ -813,7 +819,9 @@ class LibraryManager(QObject):
|
||||
ids = smart.evaluate(criteria, self.library.tracks.values(), seed=seed)
|
||||
if ids == playlist.track_ids:
|
||||
return False
|
||||
self._set_track_ids(pid, ids) # marks dirty + emits; never undoable
|
||||
# Never undoable. For a derived playlist nothing is written or timestamped
|
||||
# either — the membership lives in memory and is rebuilt on the next load.
|
||||
self._set_track_ids(pid, ids, touch=not playlist.has_derived_membership)
|
||||
return True
|
||||
|
||||
def recompute_all_smart(self, force: bool = False) -> list[str]:
|
||||
|
||||
Reference in New Issue
Block a user