✨ feat: import upload log to remove already-uploaded jobs from queue
New 'Log importieren' button in queue actions. Opens file picker for .log/.txt files, parses the fileuploader.log format: date|hoster|link||filename| Matches each log entry against queue jobs by filename+hoster (case- insensitive). Removes matching jobs that are already uploaded, shows toast with count. Use case: after a crash/restart, import the log from a previous session to skip files that were already successfully uploaded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ad9299e74c
commit
e07db0532a
26
main.js
26
main.js
@ -845,6 +845,32 @@ ipcMain.handle('import-backup', async (_event, password) => {
|
|||||||
return { ok: true, config: configStore.load() };
|
return { ok: true, config: configStore.load() };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('import-upload-log', async () => {
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
title: 'Upload-Log importieren',
|
||||||
|
filters: [
|
||||||
|
{ name: 'Log-Dateien', extensions: ['log', 'txt'] },
|
||||||
|
{ name: 'Alle Dateien', extensions: ['*'] }
|
||||||
|
],
|
||||||
|
properties: ['openFile']
|
||||||
|
});
|
||||||
|
if (canceled || !filePaths.length) return { canceled: true };
|
||||||
|
const content = fs.readFileSync(filePaths[0], 'utf-8');
|
||||||
|
// Parse log format: date|hoster|link||filename|
|
||||||
|
const entries = [];
|
||||||
|
for (const line of content.split('\n')) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
|
const parts = trimmed.split('|');
|
||||||
|
if (parts.length >= 5) {
|
||||||
|
const hoster = (parts[1] || '').trim();
|
||||||
|
const fileName = (parts[4] || '').trim();
|
||||||
|
if (hoster && fileName) entries.push({ hoster, fileName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { entries, path: filePaths[0] };
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('copy-to-clipboard', (_event, text) => {
|
ipcMain.handle('copy-to-clipboard', (_event, text) => {
|
||||||
clipboard.writeText(text);
|
clipboard.writeText(text);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -38,6 +38,9 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
finishAfterActive: () => ipcRenderer.invoke('finish-after-active'),
|
finishAfterActive: () => ipcRenderer.invoke('finish-after-active'),
|
||||||
runHealthCheck: (payload) => ipcRenderer.invoke('run-health-check', payload),
|
runHealthCheck: (payload) => ipcRenderer.invoke('run-health-check', payload),
|
||||||
|
|
||||||
|
// Log import
|
||||||
|
importUploadLog: () => ipcRenderer.invoke('import-upload-log'),
|
||||||
|
|
||||||
// Clipboard
|
// Clipboard
|
||||||
copyToClipboard: (text) => ipcRenderer.invoke('copy-to-clipboard', text),
|
copyToClipboard: (text) => ipcRenderer.invoke('copy-to-clipboard', text),
|
||||||
|
|
||||||
|
|||||||
@ -3098,6 +3098,7 @@ function setupListeners() {
|
|||||||
queueJobs.forEach(j => { if (j.status === 'error') selectedJobIds.add(j.id); });
|
queueJobs.forEach(j => { if (j.status === 'error') selectedJobIds.add(j.id); });
|
||||||
retrySelectedJobs();
|
retrySelectedJobs();
|
||||||
});
|
});
|
||||||
|
document.getElementById('importLogBtn').addEventListener('click', importUploadLog);
|
||||||
document.getElementById('confirmHosterModalBtn').addEventListener('click', applyHosterSelection);
|
document.getElementById('confirmHosterModalBtn').addEventListener('click', applyHosterSelection);
|
||||||
document.getElementById('cancelHosterModalBtn').addEventListener('click', cancelHosterModal);
|
document.getElementById('cancelHosterModalBtn').addEventListener('click', cancelHosterModal);
|
||||||
document.getElementById('closeHosterModalBtn').addEventListener('click', cancelHosterModal);
|
document.getElementById('closeHosterModalBtn').addEventListener('click', cancelHosterModal);
|
||||||
@ -3280,6 +3281,47 @@ function handleShutdownCountdown(data) {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Log import: remove already-uploaded file+hoster combos from queue ---
|
||||||
|
async function importUploadLog() {
|
||||||
|
const result = await window.api.importUploadLog();
|
||||||
|
if (!result || result.canceled) return;
|
||||||
|
const entries = result.entries || [];
|
||||||
|
if (entries.length === 0) {
|
||||||
|
showCopyToast('Keine Einträge im Log gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build lookup Set: "filename_lower|hoster"
|
||||||
|
const logKeys = new Set();
|
||||||
|
for (const entry of entries) {
|
||||||
|
logKeys.add(`${entry.fileName.toLowerCase()}|${entry.hoster.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find queue jobs that match (already uploaded)
|
||||||
|
let removed = 0;
|
||||||
|
queueJobs = queueJobs.filter(job => {
|
||||||
|
const key = `${job.fileName.toLowerCase()}|${job.hoster.toLowerCase()}`;
|
||||||
|
if (logKeys.has(key) && job.status !== 'done') {
|
||||||
|
removeJobFromIndex(job);
|
||||||
|
removed++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (removed > 0) {
|
||||||
|
selectedJobIds.clear();
|
||||||
|
syncSelectedFilesFromQueue();
|
||||||
|
rebuildJobIndex();
|
||||||
|
renderQueueTable();
|
||||||
|
updateUploadView();
|
||||||
|
updateStatusBar();
|
||||||
|
persistQueueStateSoon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
showCopyToast(`${removed} bereits hochgeladene Jobs aus Queue entfernt (${entries.length} Log-Einträge gelesen)`);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Link operations ---
|
// --- Link operations ---
|
||||||
function copyAllLinks() {
|
function copyAllLinks() {
|
||||||
const links = queueJobs
|
const links = queueJobs
|
||||||
|
|||||||
@ -94,6 +94,7 @@
|
|||||||
<div class="queue-actions" id="queueActions" style="display:none">
|
<div class="queue-actions" id="queueActions" style="display:none">
|
||||||
<button class="btn btn-xs btn-primary" id="copyAllLinksBtn">Alle Links kopieren</button>
|
<button class="btn btn-xs btn-primary" id="copyAllLinksBtn">Alle Links kopieren</button>
|
||||||
<button class="btn btn-xs btn-secondary" id="retryFailedBtn" style="display:none">Fehlgeschlagene erneut</button>
|
<button class="btn btn-xs btn-secondary" id="retryFailedBtn" style="display:none">Fehlgeschlagene erneut</button>
|
||||||
|
<button class="btn btn-xs btn-secondary" id="importLogBtn" title="Log importieren — bereits hochgeladene aus Queue entfernen">Log importieren</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="resize-handle" id="recentFilesResizer"></div>
|
<div class="resize-handle" id="recentFilesResizer"></div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user