mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-16 13:43:19 +00:00
feat(v3): show "N added / M removed" after a library scan (#686)
Completes the Refresh feature: the scanner now reports a delta so the toast can say what changed instead of a generic confirmation. Server: delete_missing returns both deltas from its one query -- rows pruned (removed) and current files not yet in the DB (added) -- and the scan retains added/removed on the terminal scan-status (previously wiped to 0). Client: the completion toast shows "N songs added / M removed" (or "up to date"), and a scan we merely attached to (background / Settings) toasts only when it actually changed something, so a periodic no-op pass stays silent. library:changed now carries the delta too. Verified end to end: empty rescan -> added 0; add a song -> added 1; remove it -> removed 1. Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
11c0f0483f
commit
7ff9261000
@@ -2586,7 +2586,10 @@ class MetadataDB:
|
|||||||
self.conn.executemany("DELETE FROM songs WHERE filename = ?", [(f,) for f in stale])
|
self.conn.executemany("DELETE FROM songs WHERE filename = ?", [(f,) for f in stale])
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self._work_display_dirty = True # membership changed → regroup
|
self._work_display_dirty = True # membership changed → regroup
|
||||||
return len(stale)
|
# Report both deltas from the one query we already ran: rows pruned,
|
||||||
|
# and how many current files are genuinely new (not yet in the DB),
|
||||||
|
# so a scan can surface an "N added / M removed" summary.
|
||||||
|
return {"removed": len(stale), "added": len(current_filenames - db_files)}
|
||||||
|
|
||||||
# ── Metadata enrichment (P7 — plumbing; the matcher itself is the next
|
# ── Metadata enrichment (P7 — plumbing; the matcher itself is the next
|
||||||
# slice) ─────────────────────────────────────────────────────────────────
|
# slice) ─────────────────────────────────────────────────────────────────
|
||||||
@@ -5034,7 +5037,7 @@ def _stat_for_cache(f: Path) -> tuple[float, int]:
|
|||||||
return st.st_mtime, st.st_size
|
return st.st_mtime, st.st_size
|
||||||
|
|
||||||
|
|
||||||
_SCAN_STATUS_INIT = {"running": False, "stage": "idle", "total": 0, "done": 0, "current": "", "error": None, "is_first_scan": False}
|
_SCAN_STATUS_INIT = {"running": False, "stage": "idle", "total": 0, "done": 0, "current": "", "error": None, "is_first_scan": False, "added": 0, "removed": 0}
|
||||||
_scan_status = dict(_SCAN_STATUS_INIT)
|
_scan_status = dict(_SCAN_STATUS_INIT)
|
||||||
|
|
||||||
_STARTUP_STATUS_INIT = {
|
_STARTUP_STATUS_INIT = {
|
||||||
@@ -5364,10 +5367,12 @@ def _background_scan():
|
|||||||
|
|
||||||
current_files = {_relpath(f, dlc) for f in all_songs}
|
current_files = {_relpath(f, dlc) for f in all_songs}
|
||||||
|
|
||||||
# Clean up stale DB entries
|
# Clean up stale DB entries. delete_missing reports both deltas (rows pruned
|
||||||
stale = meta_db.delete_missing(current_files)
|
# + genuinely-new files) so the scan can surface an added/removed summary.
|
||||||
if stale:
|
_delta = meta_db.delete_missing(current_files)
|
||||||
log.info("Removed %d stale DB entries", stale)
|
removed, added = _delta["removed"], _delta["added"]
|
||||||
|
if removed:
|
||||||
|
log.info("Removed %d stale DB entries", removed)
|
||||||
|
|
||||||
# Figure out which need scanning
|
# Figure out which need scanning
|
||||||
to_scan = []
|
to_scan = []
|
||||||
@@ -5405,7 +5410,7 @@ def _background_scan():
|
|||||||
to_scan.append((f, mtime, size, dlc))
|
to_scan.append((f, mtime, size, dlc))
|
||||||
|
|
||||||
if not to_scan:
|
if not to_scan:
|
||||||
_scan_status = {**_SCAN_STATUS_INIT, "running": True, "stage": "complete"}
|
_scan_status = {**_SCAN_STATUS_INIT, "running": True, "stage": "complete", "added": added, "removed": removed}
|
||||||
log.info("Scan: nothing new to scan (%d songs, all cached)", len(all_songs))
|
log.info("Scan: nothing new to scan (%d songs, all cached)", len(all_songs))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5430,7 +5435,7 @@ def _background_scan():
|
|||||||
_scan_status["current"] = fname
|
_scan_status["current"] = fname
|
||||||
|
|
||||||
log.info("Scan complete: %d songs cached", len(to_scan))
|
log.info("Scan complete: %d songs cached", len(to_scan))
|
||||||
_scan_status = {**_SCAN_STATUS_INIT, "running": True, "stage": "complete"}
|
_scan_status = {**_SCAN_STATUS_INIT, "running": True, "stage": "complete", "added": added, "removed": removed}
|
||||||
|
|
||||||
|
|
||||||
_scan_kick_lock = threading.Lock()
|
_scan_kick_lock = threading.Lock()
|
||||||
|
|||||||
+17
-6
@@ -2795,13 +2795,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Show a completion toast (reuses the shared fbNotify surface), suppressed
|
// Show a completion toast (reuses the shared fbNotify surface), suppressed
|
||||||
// while in a song. Honest + never-punishing copy; the precise "N added" count
|
// while in a song. Honest + never-punishing copy: shows the "N added / M
|
||||||
// arrives with the background-scan delta work — until then this is a generic,
|
// removed" delta from the scan when there is one, else "up to date".
|
||||||
// truthful confirmation.
|
|
||||||
function _scanCompleteToast(sd) {
|
function _scanCompleteToast(sd) {
|
||||||
if (document.querySelector('.screen.active') && document.querySelector('.screen.active').id === 'player') return;
|
if (document.querySelector('.screen.active') && document.querySelector('.screen.active').id === 'player') return;
|
||||||
if (!window.fbNotify) return;
|
if (!window.fbNotify) return;
|
||||||
const msg = (sd && sd.error) ? 'Scan finished with an error' : 'Your library is up to date';
|
const added = (sd && sd.added) || 0, removed = (sd && sd.removed) || 0;
|
||||||
|
let msg;
|
||||||
|
if (sd && sd.error) msg = 'Scan finished with an error';
|
||||||
|
else if (added || removed) {
|
||||||
|
const parts = [];
|
||||||
|
if (added) parts.push(added + ' song' + (added === 1 ? '' : 's') + ' added');
|
||||||
|
if (removed) parts.push(removed + ' removed');
|
||||||
|
msg = parts.join(' · ');
|
||||||
|
} else msg = 'Your library is up to date';
|
||||||
try { window.fbNotify.show({ title: 'Library scan complete', message: msg, icon: '🔄', accent: '#22C55E' }); } catch (e) { /* */ }
|
try { window.fbNotify.show({ title: 'Library scan complete', message: msg, icon: '🔄', accent: '#22C55E' }); } catch (e) { /* */ }
|
||||||
}
|
}
|
||||||
// Poll scan-status until the scan finishes, driving the button state. On
|
// Poll scan-status until the scan finishes, driving the button state. On
|
||||||
@@ -2824,8 +2831,12 @@
|
|||||||
if ((sawRunning && sd && !sd.running) || noopDone || ticks >= 180) {
|
if ((sawRunning && sd && !sd.running) || noopDone || ticks >= 180) {
|
||||||
clearInterval(_refreshPoll); _refreshPoll = null;
|
clearInterval(_refreshPoll); _refreshPoll = null;
|
||||||
_setRefreshState(null);
|
_setRefreshState(null);
|
||||||
if (sawRunning && window.feedBack) { try { window.feedBack.emit('library:changed', { reason: 'rescan' }); } catch (e) { /* */ } }
|
const hasDelta = sd && (((sd.added || 0) > 0) || ((sd.removed || 0) > 0));
|
||||||
if (announce) _scanCompleteToast(sd);
|
if (sawRunning && window.feedBack) { try { window.feedBack.emit('library:changed', { reason: 'rescan', added: (sd && sd.added) || 0, removed: (sd && sd.removed) || 0 }); } catch (e) { /* */ } }
|
||||||
|
// A user-initiated refresh always confirms; a scan we only attached
|
||||||
|
// to (background / Settings) toasts just when it changed something,
|
||||||
|
// so a periodic no-op pass stays silent.
|
||||||
|
if (announce || hasDelta) _scanCompleteToast(sd);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user