fix: purge bad download snapshots from UI store and make corrupted file deletion idempotent

This commit is contained in:
Dariusz L
2026-11-01 17:41:52 +01:00
parent 6c8b4e7002
commit 9227ca2c45
3 changed files with 182 additions and 54 deletions
+23 -1
View File
@@ -323,9 +323,31 @@ def delete_badsha_download(
target_bad_path = f"{candidate}.badsha"
if not target_bad_path and path:
target_bad_path = str(path).strip()
cand = str(path).strip()
if cand.lower().endswith(".badsha"):
target_bad_path = cand
elif cand and facade.os.path.isfile(f"{cand}.badsha"):
target_bad_path = f"{cand}.badsha"
elif cand:
target_bad_path = cand
if not target_bad_path:
if download_id:
with facade.download_lock:
facade.download_progress.pop(download_id, None)
facade.cancelled_downloads.discard(download_id)
remove_persisted = getattr(facade, "remove_persisted_download", None)
if callable(remove_persisted):
try:
remove_persisted(download_id)
except Exception:
pass
return {
"success": True,
"already_removed": True,
"message": "Corrupted download already removed",
"removed_files": [],
}
return {"success": False, "error": "No .badsha file path found"}
normalized = facade.os.path.normpath(target_bad_path)
+16
View File
@@ -782,6 +782,22 @@ class DownloaderBoundaryTests(unittest.TestCase):
self.assertIn("only files ending with .badsha", result["error"].lower())
self.assertTrue(os.path.exists(safe_model))
def test_delete_badsha_download_idempotent_when_already_cleared(self):
result = delete_badsha_download(download_id="already-removed-id", dependencies=downloader)
self.assertTrue(result["success"])
self.assertTrue(result.get("already_removed"))
def test_delete_badsha_download_finds_badsha_from_base_path(self):
with tempfile.TemporaryDirectory() as temp_dir:
base_model = os.path.join(temp_dir, "base_model.safetensors")
badsha_file = f"{base_model}.badsha"
with open(badsha_file, "wb") as f:
f.write(b"corrupt payload")
result = delete_badsha_download(path=base_model, dependencies=downloader)
self.assertTrue(result["success"])
self.assertFalse(os.path.exists(badsha_file))
def test_parse_retry_after(self):
self.assertEqual(parse_retry_after("120"), 120)
self.assertEqual(parse_retry_after("0"), 1)
+143 -53
View File
@@ -504,6 +504,21 @@ export const resolveDownloadMethods = {
return next;
},
clearDownloadSnapshot(downloadId, missing = null) {
const store = this.getDownloadProgressStore?.();
if (store instanceof Map) {
const missingKey = missing ? this.getDownloadStateKey(missing) : null;
for (const [key, snap] of Array.from(store.entries())) {
if (
(downloadId && String(snap?.downloadId || '') === String(downloadId))
|| (missingKey && key === missingKey)
) {
store.delete(key);
}
}
}
},
cloneLocalMatches(matches = []) {
if (!Array.isArray(matches)) return [];
try {
@@ -1122,12 +1137,32 @@ export const resolveDownloadMethods = {
return { progressDiv, downloadBtn };
},
async deleteBadDownload(downloadId) {
async deleteBadDownload(downloadId) {
if (!downloadId) return;
const info = this.activeDownloads?.[downloadId];
const badPath = info?.lastProgress?.bad_path || info?.statusSnapshot?.progress?.bad_path || '';
const activeInfo = this.activeDownloads?.[downloadId];
let snapshotInfo = null;
const store = this.getDownloadProgressStore?.();
if (store instanceof Map) {
for (const [, snap] of store.entries()) {
if (String(snap?.downloadId || '') === String(downloadId)) {
snapshotInfo = snap;
break;
}
}
}
const info = activeInfo || snapshotInfo || null;
const missing = activeInfo?.missing || snapshotInfo?.missing || null;
const badPath = activeInfo?.lastProgress?.bad_path
|| activeInfo?.statusSnapshot?.progress?.bad_path
|| snapshotInfo?.progress?.bad_path
|| snapshotInfo?.bad_path
|| activeInfo?.downloadPath
|| snapshotInfo?.downloadPath
|| '';
let res = null;
try {
const res = await this.fetchJson(
res = await this.fetchJson(
`/model_resolver/delete_bad_download/${encodeURIComponent(downloadId)}`,
{
method: 'POST',
@@ -1136,30 +1171,55 @@ export const resolveDownloadMethods = {
},
'Delete corrupted download'
);
if (res && res.success) {
this.showNotification('Corrupted (.badsha) file deleted', 'info');
delete this.activeDownloads?.[downloadId];
this.removeActiveDownloadRecovery?.(downloadId);
this.updateDownloadAllButtonState?.();
this.updateQueuePanel?.();
if (info?.missing) {
const elements = this.resolveDownloadUiElements(info);
if (elements.progressDiv) {
elements.progressDiv.classList.add('mr-is-hidden');
elements.progressDiv.classList.remove('mr-is-visible');
elements.progressDiv.innerHTML = '';
}
if (elements.downloadBtn) {
this.restoreDownloadButtonReadyState(elements.downloadBtn);
}
this.refreshLocalMatchesUiForMissing?.(info.missing);
}
} else {
this.showNotification(res?.error || 'Failed to delete corrupted file', 'error');
}
} catch (err) {
console.error('Model Resolver: Failed to delete corrupted download:', err);
this.showNotification('Failed to delete corrupted file: ' + err.message, 'error');
console.warn('Model Resolver: Delete bad download request error:', err);
}
const wasSuccess = Boolean(res && res.success);
delete this.activeDownloads?.[downloadId];
this.removeActiveDownloadRecovery?.(downloadId);
this.clearDownloadSnapshot(downloadId, missing);
const targetMissing = missing || info?.missing || null;
if (targetMissing) {
const elements = this.resolveDownloadUiElements(info || { missing: targetMissing });
if (elements.progressDiv) {
elements.progressDiv.classList.add('mr-is-hidden');
elements.progressDiv.classList.remove('mr-is-visible');
elements.progressDiv.innerHTML = '';
}
if (elements.downloadBtn) {
this.restoreDownloadButtonReadyState(elements.downloadBtn);
}
this.refreshLocalMatchesUiForMissing?.(targetMissing);
}
const root = this.contentElement || (typeof document !== 'undefined' ? document : null);
if (root?.querySelectorAll) {
const safeId = String(downloadId).replace(/["\\]/g, '\\$&');
const queueItems = root.querySelectorAll(`.mr-download-queue-item[data-download-id="${safeId}"]`);
for (const item of queueItems) {
const progressSlot = item.closest?.('.mr-download-progress-slot');
if (progressSlot) {
progressSlot.classList.add('mr-is-hidden');
progressSlot.classList.remove('mr-is-visible');
progressSlot.innerHTML = '';
} else {
item.remove();
}
}
}
this.updateDownloadAllButtonState?.();
this.updateQueuePanel?.();
if (wasSuccess) {
this.showNotification(
res?.already_removed ? 'Corrupted download removed' : 'Corrupted (.badsha) file deleted',
'info'
);
} else if (res?.error) {
this.showNotification(res.error, 'warning');
}
},
@@ -2394,46 +2454,76 @@ export const resolveDownloadMethods = {
* Cancel an active download
*/
cancelDownload(downloadId) {
const info = this.activeDownloads[downloadId];
const info = this.activeDownloads?.[downloadId];
let snapshotInfo = null;
if (!info) {
const store = this.getDownloadProgressStore?.();
if (store instanceof Map) {
for (const [, snap] of store.entries()) {
if (String(snap?.downloadId || '') === String(downloadId)) {
snapshotInfo = snap;
break;
}
}
}
}
const effectiveInfo = info || snapshotInfo || null;
const currentStatus = String(
info?.pendingDownloadStatus
|| info?.lastStatus
|| info?.lastProgress?.status
effectiveInfo?.pendingDownloadStatus
|| effectiveInfo?.lastStatus
|| effectiveInfo?.lastProgress?.status
|| effectiveInfo?.status
|| ''
).toLowerCase();
if (currentStatus === 'error' || currentStatus === 'refresh_error') {
delete this.activeDownloads[downloadId];
delete this.activeDownloads?.[downloadId];
this.removeActiveDownloadRecovery?.(downloadId);
if (info) {
const elements = this.resolveDownloadUiElements?.(info) || {};
const cancelledSnapshot = this.rememberDownloadUiState(
downloadId,
info,
{ ...(info.lastProgress || {}), status: 'cancelled' },
{
status: 'cancelled',
message: 'Download dismissed',
type: 'info',
isActive: false
}
);
this.renderDownloadSnapshot?.(downloadId, cancelledSnapshot, elements);
this.refreshLocalMatchesUiForMissing?.(info.missing);
this.clearDownloadSnapshot(downloadId, effectiveInfo?.missing);
const targetMissing = effectiveInfo?.missing || null;
if (targetMissing) {
const elements = this.resolveDownloadUiElements?.(effectiveInfo || { missing: targetMissing }) || {};
if (elements.progressDiv) {
elements.progressDiv.classList.add('mr-is-hidden');
elements.progressDiv.classList.remove('mr-is-visible');
elements.progressDiv.innerHTML = '';
}
if (elements.downloadBtn) {
this.restoreDownloadButtonReadyState?.(elements.downloadBtn);
}
this.refreshLocalMatchesUiForMissing?.(targetMissing);
}
const root = this.contentElement || (typeof document !== 'undefined' ? document : null);
if (root?.querySelectorAll) {
const safeId = String(downloadId).replace(/["\\]/g, '\\$&');
const queueItems = root.querySelectorAll(`.mr-download-queue-item[data-download-id="${safeId}"]`);
for (const item of queueItems) {
const progressSlot = item.closest?.('.mr-download-progress-slot');
if (progressSlot) {
progressSlot.classList.add('mr-is-hidden');
progressSlot.classList.remove('mr-is-visible');
progressSlot.innerHTML = '';
} else {
item.remove();
}
}
}
this.updateDownloadAllButtonState?.();
this.updateQueuePanel?.();
this.fetchJson(`/model_resolver/cancel/${downloadId}`, {
this.fetchJson(`/model_resolver/cancel/${encodeURIComponent(downloadId)}`, {
method: 'POST'
}, 'Cancel download').catch(() => {});
return;
}
if (info) {
this.setPendingDownloadStatus(info, 'cancelling', 30000);
if (effectiveInfo) {
this.setPendingDownloadStatus(effectiveInfo, 'cancelling', 30000);
const snapshot = this.rememberDownloadUiState(
downloadId,
info,
effectiveInfo,
{ ...(info.lastProgress || {}), status: 'cancelling' },
{
status: 'cancelling',
@@ -2442,7 +2532,7 @@ export const resolveDownloadMethods = {
isActive: true
}
);
const elements = this.resolveDownloadUiElements(info);
const elements = this.resolveDownloadUiElements(effectiveInfo);
this.renderDownloadSnapshot(downloadId, snapshot, elements);
this.updateQueuePanel?.();
}
@@ -2453,7 +2543,7 @@ export const resolveDownloadMethods = {
// The request only acknowledges cancellation. Progress polling
// releases the file and enables retry when the worker has stopped.
}).catch((error) => {
this.clearPendingDownloadStatus(info);
if (effectiveInfo) this.clearPendingDownloadStatus(effectiveInfo);
console.error('Model Resolver: Cancel error:', error);
this.showNotification('Failed to cancel download', 'error');
});