Gate active batch additions on cleanup persistence
Route folder-monitor and hoster-selection additions through the shared active-batch start path so revoked source-cleanup confirmations are durably saved before Main receives any new job. Add real Electron coverage for blocked persistence, ordered success, and rejected saves across both entry points.
This commit is contained in:
+4
-24
@@ -494,7 +494,7 @@ async function init() {
|
||||
});
|
||||
|
||||
// Folder monitor: auto-queue new files
|
||||
window.api.onFolderMonitorNewFiles((files) => {
|
||||
window.api.onFolderMonitorNewFiles(async (files) => {
|
||||
window.api.debugLog('folder-monitor: received ' + files.length + ' file(s)');
|
||||
const fm = config.globalSettings && config.globalSettings.folderMonitor;
|
||||
const fmHosters = fm && Array.isArray(fm.hosters) && fm.hosters.length > 0 ? fm.hosters : [];
|
||||
@@ -524,17 +524,7 @@ async function init() {
|
||||
// Inject new preview jobs into the running batch
|
||||
const newJobs = queueJobs.filter(j => j.status === 'preview' && newPaths.has(j.file));
|
||||
if (newJobs.length > 0) {
|
||||
const cleanupPreparation = prepareSourceCleanup(newJobs);
|
||||
newJobs.forEach(j => { j.status = 'queued'; });
|
||||
renderQueueTable();
|
||||
window.api.addJobsToBatch({
|
||||
jobs: newJobs.map(serializeUploadJob),
|
||||
sourceCleanupGroups: cleanupPreparation.groups
|
||||
}).then(result => {
|
||||
_markSkippedJobs(result);
|
||||
if (result?.sourceCleanupFingerprints && window.SourceCleanupPolicy) window.SourceCleanupPolicy.applyFingerprints(queueJobs, result.sourceCleanupFingerprints);
|
||||
}).catch(() => {});
|
||||
persistQueueStateSoon(true);
|
||||
await startSelectedUpload(newJobs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1139,7 +1129,7 @@ function closeHosterModal() {
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
function applyHosterSelection() {
|
||||
async function applyHosterSelection() {
|
||||
selectedUploadHosters = Array.from(document.querySelectorAll('input[data-hoster-modal]:checked'))
|
||||
.map(input => input.dataset.hosterModal);
|
||||
// Move pending files to selectedFiles on confirm
|
||||
@@ -1158,17 +1148,7 @@ function applyHosterSelection() {
|
||||
buildQueuePreview(); // creates 'preview' jobs for new files
|
||||
const newJobs = queueJobs.filter(j => j.status === 'preview' && pendingPaths.has(j.file));
|
||||
if (newJobs.length > 0) {
|
||||
const cleanupPreparation = prepareSourceCleanup(newJobs);
|
||||
newJobs.forEach(j => { j.status = 'queued'; });
|
||||
renderQueueTable();
|
||||
window.api.addJobsToBatch({
|
||||
jobs: newJobs.map(serializeUploadJob),
|
||||
sourceCleanupGroups: cleanupPreparation.groups
|
||||
}).then(result => {
|
||||
_markSkippedJobs(result);
|
||||
if (result?.sourceCleanupFingerprints && window.SourceCleanupPolicy) window.SourceCleanupPolicy.applyFingerprints(queueJobs, result.sourceCleanupFingerprints);
|
||||
}).catch(() => {});
|
||||
persistQueueStateSoon(true);
|
||||
await startSelectedUpload(newJobs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1725,6 +1725,91 @@ setTimeout(async () => {
|
||||
restoreInitialIpcHandler('save-pending-queue');
|
||||
await wc.executeJavaScript('flushConfigWrites()');
|
||||
check('A failed cleanup revocation save stays mandatory for the next start attempt', sourceCleanupRevocationRetry.firstFailed === true && sourceCleanupRevocationRetry.secondSucceeded === true && sourceCleanupRevocationRetry.secondRevocations.length === 0 && sourceCleanupRevocationCallsBeforeRecovery >= 2);
|
||||
const activeBatchSeed = async (file, token, mode) => {
|
||||
const seed = JSON.stringify({ file, token, mode });
|
||||
const result = await wc.executeJavaScript('(() => { try { const seed = ' + seed + '; queuePersistThrottle.cancel(); selectedFiles = []; _pendingFiles = []; selectedUploadHosters = ["voe.sx"]; uploading = true; config.globalSettings = { ...(config.globalSettings || {}), deleteSourceAfterSuccessfulUpload: true, folderMonitor: { ...(config.globalSettings?.folderMonitor || {}), hosters: ["voe.sx"], autoStart: false } }; queueJobs = [{ id: "ui-active-existing-" + seed.mode, file: seed.file, fileName: "active-existing.bin", hoster: "byse.sx", status: "error", bytesTotal: 10, sourceCleanupMetadataVersion: 2, sourceCleanupToken: seed.token, sourceCleanupRequiredHosters: ["voe.sx", "byse.sx"], sourceCleanupConfirmedHosters: ["voe.sx"] }]; rebuildJobIndex(); if (seed.mode === "selection") { const list = document.getElementById("hosterModalList"); const input = document.createElement("input"); input.type = "checkbox"; input.dataset.hosterModal = "voe.sx"; input.checked = true; list.replaceChildren(input); _pendingFiles = [{ path: seed.file, name: "active-selection.bin", size: 10 }]; } return { ok: true }; } catch (error) { return { ok: false, error: String(error && (error.stack || error.message) || error) }; } })()');
|
||||
if (!result?.ok) throw new Error('Active-batch seed failed: ' + result?.error);
|
||||
};
|
||||
const runActiveBatchBarrierSuccess = async (mode) => {
|
||||
const file = 'C:/ui/active-' + mode + '-barrier.bin';
|
||||
await wc.executeJavaScript('flushConfigWrites()');
|
||||
await activeBatchSeed(file, 'ui-active-' + mode + '-barrier-token', mode);
|
||||
const order = [];
|
||||
let addCalls = 0;
|
||||
let saveCalls = 0;
|
||||
let releaseSave = null;
|
||||
ipcMain.removeHandler('save-pending-queue');
|
||||
ipcMain.handle('save-pending-queue', () => {
|
||||
saveCalls++;
|
||||
order.push('save-start-' + saveCalls);
|
||||
if (saveCalls !== 1) {
|
||||
order.push('save-done-' + saveCalls);
|
||||
return true;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
releaseSave = () => {
|
||||
order.push('save-done-1');
|
||||
resolve(true);
|
||||
};
|
||||
});
|
||||
});
|
||||
ipcMain.removeHandler('add-jobs-to-batch');
|
||||
ipcMain.handle('add-jobs-to-batch', () => {
|
||||
addCalls++;
|
||||
order.push('add');
|
||||
return { added: 1, skippedJobs: [], sourceCleanupFingerprints: {} };
|
||||
});
|
||||
if (mode === 'folder') wc.send('folder-monitor:new-files', [file]);
|
||||
else await wc.executeJavaScript('void applyHosterSelection()');
|
||||
await waitUntil(() => releaseSave !== null);
|
||||
const blockedBeforeSave = addCalls === 0;
|
||||
releaseSave?.();
|
||||
await waitUntil(() => addCalls === 1);
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
return { blockedBeforeSave, addCalls, order };
|
||||
};
|
||||
const folderBarrierSuccess = await runActiveBatchBarrierSuccess('folder');
|
||||
check('Folder monitor active-batch add waits for cleanup revocation persistence', folderBarrierSuccess.blockedBeforeSave === true && folderBarrierSuccess.addCalls === 1 && folderBarrierSuccess.order.indexOf('save-done-1') < folderBarrierSuccess.order.indexOf('add'));
|
||||
restoreInitialIpcHandler('save-pending-queue');
|
||||
restoreInitialIpcHandler('add-jobs-to-batch');
|
||||
await wc.executeJavaScript('persistSourceCleanupRevocations({ revokedHosters: [] })');
|
||||
const selectionBarrierSuccess = await runActiveBatchBarrierSuccess('selection');
|
||||
check('Hoster selection active-batch add waits for cleanup revocation persistence', selectionBarrierSuccess.blockedBeforeSave === true && selectionBarrierSuccess.addCalls === 1 && selectionBarrierSuccess.order.indexOf('save-done-1') < selectionBarrierSuccess.order.indexOf('add'));
|
||||
restoreInitialIpcHandler('save-pending-queue');
|
||||
restoreInitialIpcHandler('add-jobs-to-batch');
|
||||
await wc.executeJavaScript('persistSourceCleanupRevocations({ revokedHosters: [] })');
|
||||
const runActiveBatchBarrierFailure = async (mode) => {
|
||||
const file = 'C:/ui/active-' + mode + '-rejected.bin';
|
||||
await wc.executeJavaScript('flushConfigWrites()');
|
||||
await activeBatchSeed(file, 'ui-active-' + mode + '-rejected-token', mode);
|
||||
let addCalls = 0;
|
||||
let saveCalls = 0;
|
||||
ipcMain.removeHandler('save-pending-queue');
|
||||
ipcMain.handle('save-pending-queue', () => {
|
||||
saveCalls++;
|
||||
throw new Error('injected active-batch revocation save failure');
|
||||
});
|
||||
ipcMain.removeHandler('add-jobs-to-batch');
|
||||
ipcMain.handle('add-jobs-to-batch', () => {
|
||||
addCalls++;
|
||||
return { added: 1, skippedJobs: [], sourceCleanupFingerprints: {} };
|
||||
});
|
||||
if (mode === 'folder') wc.send('folder-monitor:new-files', [file]);
|
||||
else await wc.executeJavaScript('void applyHosterSelection()');
|
||||
await waitUntil(() => saveCalls > 0);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
return { addCalls, saveCalls };
|
||||
};
|
||||
const folderBarrierFailure = await runActiveBatchBarrierFailure('folder');
|
||||
check('Rejected folder monitor cleanup revocation save prevents the active-batch add', folderBarrierFailure.saveCalls > 0 && folderBarrierFailure.addCalls === 0);
|
||||
restoreInitialIpcHandler('save-pending-queue');
|
||||
restoreInitialIpcHandler('add-jobs-to-batch');
|
||||
await wc.executeJavaScript('persistSourceCleanupRevocations({ revokedHosters: [] })');
|
||||
const selectionBarrierFailure = await runActiveBatchBarrierFailure('selection');
|
||||
check('Rejected hoster selection cleanup revocation save prevents the active-batch add', selectionBarrierFailure.saveCalls > 0 && selectionBarrierFailure.addCalls === 0);
|
||||
restoreInitialIpcHandler('save-pending-queue');
|
||||
restoreInitialIpcHandler('add-jobs-to-batch');
|
||||
await wc.executeJavaScript('persistSourceCleanupRevocations({ revokedHosters: [] }); uploading = false; selectedFiles = []; _pendingFiles = []; queueJobs = []; rebuildJobIndex()');
|
||||
const sourceCleanupFinalizationGate = await wc.executeJavaScript('(async () => { if (typeof sourceCleanupFinalizationPending === "undefined") return { available: false }; queueJobs = [{ id: "ui-cleanup-finalizing", file: "C:/ui/cleanup-finalizing.bin", fileName: "cleanup-finalizing.bin", hoster: "voe.sx", status: "done", bytesTotal: 10, result: { download_url: "https://example.invalid/finalizing" } }]; rebuildJobIndex(); selectedJobIds.clear(); selectedJobIds.add(queueJobs[0].id); sourceCleanupFinalizationPending = true; updateQueueActionButtons(); const disabled = document.getElementById("reuploadSelectedBtn")?.disabled === true; await retrySelectedJobs(); const status = queueJobs[0].status; sourceCleanupFinalizationPending = false; selectedJobIds.clear(); updateQueueActionButtons(); return { available: true, disabled, status }; })()');
|
||||
check('Final cleanup persistence blocks a new retry until the handshake settles', sourceCleanupFinalizationGate.available === true && sourceCleanupFinalizationGate.disabled === true && sourceCleanupFinalizationGate.status === 'done');
|
||||
let sourceCleanupFinalizationPayload = null;
|
||||
|
||||
Reference in New Issue
Block a user