From e07db0532a43bed46baf54061be20910786f90b1 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 24 Mar 2026 09:57:09 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20import=20upload=20log=20to?= =?UTF-8?q?=20remove=20already-uploaded=20jobs=20from=20queue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- main.js | 26 ++++++++++++++++++++++++++ preload.js | 3 +++ renderer/app.js | 42 ++++++++++++++++++++++++++++++++++++++++++ renderer/index.html | 1 + 4 files changed, 72 insertions(+) diff --git a/main.js b/main.js index 2709d4a..b8304e2 100644 --- a/main.js +++ b/main.js @@ -845,6 +845,32 @@ ipcMain.handle('import-backup', async (_event, password) => { 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) => { clipboard.writeText(text); return true; diff --git a/preload.js b/preload.js index 27f9318..705e0be 100644 --- a/preload.js +++ b/preload.js @@ -38,6 +38,9 @@ contextBridge.exposeInMainWorld('api', { finishAfterActive: () => ipcRenderer.invoke('finish-after-active'), runHealthCheck: (payload) => ipcRenderer.invoke('run-health-check', payload), + // Log import + importUploadLog: () => ipcRenderer.invoke('import-upload-log'), + // Clipboard copyToClipboard: (text) => ipcRenderer.invoke('copy-to-clipboard', text), diff --git a/renderer/app.js b/renderer/app.js index 52d0fd0..8a9978b 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -3098,6 +3098,7 @@ function setupListeners() { queueJobs.forEach(j => { if (j.status === 'error') selectedJobIds.add(j.id); }); retrySelectedJobs(); }); + document.getElementById('importLogBtn').addEventListener('click', importUploadLog); document.getElementById('confirmHosterModalBtn').addEventListener('click', applyHosterSelection); document.getElementById('cancelHosterModalBtn').addEventListener('click', cancelHosterModal); document.getElementById('closeHosterModalBtn').addEventListener('click', cancelHosterModal); @@ -3280,6 +3281,47 @@ function handleShutdownCountdown(data) { }, 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 --- function copyAllLinks() { const links = queueJobs diff --git a/renderer/index.html b/renderer/index.html index eaff136..997f820 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -94,6 +94,7 @@