fix(quarantine): auto-delete siblings + cancel retry on approve (#920)
Multiple quarantine entries accumulate per track (one per retry attempt) and approving one left the others behind and kept the retry running. - Approve now sends remove_siblings=true — backend sibling deletion was already implemented but the flag was never passed from the UI - Toast message now reports how many duplicate candidates were removed - After approve, the backend cancels any in-flight quarantine-retry task for the same track (matched by title) so the engine stops fetching new candidates once the user has already accepted one - Approve All also sends remove_siblings=true Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
55f7176c34
commit
842bc0ab34
2 changed files with 37 additions and 3 deletions
|
|
@ -7668,11 +7668,34 @@ def approve_quarantine_item(entry_id):
|
|||
logger.info(f"[Quarantine] Auto-removed {len(removed_siblings)} sibling alternative(s) of {entry_id}: {removed_siblings}")
|
||||
except Exception as sib_exc:
|
||||
logger.warning(f"[Quarantine] Sibling cleanup for {entry_id} failed: {sib_exc}")
|
||||
# Cancel any still-running quarantine-retry task for the same track so
|
||||
# the engine doesn't keep fetching new candidates after the user has
|
||||
# already accepted one. Match by track title from the restored context.
|
||||
cancelled_retry_task = None
|
||||
try:
|
||||
ti = context.get('track_info') if isinstance(context.get('track_info'), dict) else {}
|
||||
_approved_name = (ti.get('name') or '').strip().lower()
|
||||
if _approved_name:
|
||||
with tasks_lock:
|
||||
for _tid, _t in download_tasks.items():
|
||||
if not _t.get('_quarantine_retry'):
|
||||
continue
|
||||
if _t.get('status') in ('completed', 'cancelled', 'failed'):
|
||||
continue
|
||||
_tti = _t.get('track_info') if isinstance(_t.get('track_info'), dict) else {}
|
||||
if (_tti.get('name') or '').strip().lower() == _approved_name:
|
||||
_t['status'] = 'cancelled'
|
||||
cancelled_retry_task = _tid
|
||||
logger.info(f"[Quarantine] Cancelled in-flight retry task {_tid} for '{_approved_name}' (user approved alternative)")
|
||||
break
|
||||
except Exception as _crt_exc:
|
||||
logger.debug(f"[Quarantine] Retry-cancel scan failed: {_crt_exc}")
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"trigger_bypassed": "all",
|
||||
"original_trigger": trigger,
|
||||
"removed_siblings": removed_siblings,
|
||||
"cancelled_retry_task": cancelled_retry_task,
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"[Quarantine] Error approving {entry_id}: {e}")
|
||||
|
|
|
|||
|
|
@ -2786,10 +2786,17 @@ async function verifQuarApprove(idx, btn) {
|
|||
const q = _verifQuarEntries[idx]; if (!q) return;
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(q.id)}/approve`, { method: 'POST' });
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(q.id)}/approve`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ remove_siblings: true }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.success) {
|
||||
showToast && showToast('Approved — re-importing, will be marked human-verified 🛡✔', 'success');
|
||||
const extra = d.removed_siblings && d.removed_siblings.length
|
||||
? ` (${d.removed_siblings.length} duplicate candidate${d.removed_siblings.length > 1 ? 's' : ''} removed)`
|
||||
: '';
|
||||
showToast && showToast(`Approved — re-importing, will be marked human-verified 🛡✔${extra}`, 'success');
|
||||
_verifLoadQuarantine(true);
|
||||
} else {
|
||||
showToast && showToast(d.error || 'Approve failed', 'error');
|
||||
|
|
@ -2905,7 +2912,11 @@ async function verifQuarApproveAll(btn) {
|
|||
let ok = 0;
|
||||
for (const q of entries) {
|
||||
try {
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(q.id)}/approve`, { method: 'POST' });
|
||||
const r = await fetch(`/api/quarantine/${encodeURIComponent(q.id)}/approve`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ remove_siblings: true }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.success) ok++;
|
||||
} catch (e) {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue