v0.7.0: startup speed — the 21k-track library stops freezing
Three reported symptoms, one shape: every operation was whole-library, whole-file, on the Qt main thread. Measured on the real library (21,482 tracks, 506 playlists, 15 MB library.json). The track table sorted through a QSortFilterProxyModel, which asks data() for a value on every comparison — 580k Python round trips, 8.5 s per table load, paid again on every reload. TrackTableModel now keeps _tracks in canonical (playlist) order plus an _order index list and sorts a key list computed once per track. Nothing used the proxy's filtering. Sorting by # is the identity order, so "source row" still means "playlist position" for drag-reorder. 8.5 s -> 0.13 s; MainWindow() 18.1 s -> 0.79 s. Smart rules compile to closures once per evaluate() instead of being re-dispatched per track — the operator lookup, casefolding the query and parsing the rule's own date constants all leave the 21k-iteration loop (_match_date was re-parsing its own constant 21,482 times per rule). Verified identical membership against the old evaluator on all 20 real smart playlists. 2.54 s -> 0.72 s, and it now runs after the first paint. Also: _reconcile_track skips two to_dict() round trips per unchanged track (MERGEABLE_FIELDS in the resolver is the single source of truth for what a merge touches); no git subprocess on the startup path (Updater.enabled is probed lazily on the background thread, version-button tooltip deferred); and .resolved/ is pruned to the newest 10 snapshots — it had reached 1.7 GB across 73 snapshots inside the Syncthing share. Time to interactive window ~15 s -> 3.2 s. The mid-session freeze when Syncthing delivers a change — a full reload + recompute + table rebuild with the window already on screen, which is what GNOME was offering to force-quit — ~16 s -> 3.8 s. The merge rework itself (playlist ordering, per-machine play journal, quieting the dialog) is queued in TASKS.md as Round 36.
This commit is contained in:
@@ -930,7 +930,14 @@ def _reconcile_track(mem_track, disk_track):
|
||||
"""Fold the disk copy of a track into the in-memory one using the same rules
|
||||
as the conflict resolver (max play/skip counts, newest date_modified wins),
|
||||
mutating mem_track in place so any view holding it stays valid."""
|
||||
from lintunes.storage.conflict_resolver import _merge_track_fields
|
||||
from lintunes.storage.conflict_resolver import (
|
||||
MERGEABLE_FIELDS, _merge_track_fields)
|
||||
# A sync typically changes a handful of tracks out of tens of thousands.
|
||||
# Comparing the mergeable fields first skips two to_dict() round trips per
|
||||
# untouched track, which is most of the cost of a reload.
|
||||
if all(getattr(mem_track, f, None) == getattr(disk_track, f, None)
|
||||
for f in MERGEABLE_FIELDS):
|
||||
return
|
||||
merged = mem_track.to_dict()
|
||||
_merge_track_fields(merged, disk_track.to_dict()) # disk wins where newer
|
||||
for key, value in merged.items():
|
||||
|
||||
Reference in New Issue
Block a user