diff --git a/main.js b/main.js index b76fdeb..14e4663 100644 --- a/main.js +++ b/main.js @@ -113,16 +113,37 @@ function getLogFilePath() { return _dailyLogPath; } +let _uploadLogFallbackWarned = false; function appendUploadLog(hoster, link, fileName) { + const now = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + const dateStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`; + const line = `${dateStr}|${hoster}|${link}||${fileName}|\n`; + + const tryWrite = (p) => { + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.appendFileSync(p, line, 'utf-8'); + }; + try { - const logPath = getLogFilePath(); - fs.mkdirSync(path.dirname(logPath), { recursive: true }); - const now = new Date(); - const pad = (n) => String(n).padStart(2, '0'); - const dateStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`; - const line = `${dateStr}|${hoster}|${link}||${fileName}|\n`; - fs.appendFileSync(logPath, line, 'utf-8'); - } catch {} + tryWrite(getLogFilePath()); + return; + } catch (err) { + debugLog(`appendUploadLog primary failed (${err.message}); using fallback`); + } + + try { + const fallbackPath = path.join(app.getPath('userData'), 'fileuploader-fallback.log'); + tryWrite(fallbackPath); + if (!_uploadLogFallbackWarned) { + _uploadLogFallbackWarned = true; + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('upload-log-fallback', { fallbackPath }); + } + } + } catch (err) { + debugLog(`appendUploadLog fallback also failed: ${err.message}`); + } } function flattenHistoryForExport(history) { @@ -681,6 +702,21 @@ ipcMain.handle('get-history', () => { return configStore.loadHistory(); }); +ipcMain.handle('save-text-file', async (_event, defaultName, content, filters) => { + const safeName = String(defaultName || `export-${new Date().toISOString().slice(0, 10)}.txt`); + const safeFilters = Array.isArray(filters) && filters.length + ? filters + : [{ name: 'Textdatei', extensions: ['txt', 'csv', 'log'] }]; + const { canceled, filePath } = await dialog.showSaveDialog(mainWindow, { + title: 'Speichern unter', + defaultPath: safeName, + filters: safeFilters + }); + if (canceled || !filePath) return { ok: false, canceled: true }; + fs.writeFileSync(filePath, String(content === null || content === undefined ? '' : content), 'utf-8'); + return { ok: true, path: filePath }; +}); + ipcMain.handle('export-history', async (_event, format) => { const normalizedFormat = String(format || 'csv').toLowerCase() === 'json' ? 'json' : 'csv'; const history = configStore.loadHistory(); diff --git a/preload.js b/preload.js index 4f0098e..9533637 100644 --- a/preload.js +++ b/preload.js @@ -7,6 +7,7 @@ contextBridge.exposeInMainWorld('api', { getHistory: () => ipcRenderer.invoke('get-history'), clearHistory: () => ipcRenderer.invoke('clear-history'), exportHistory: (format) => ipcRenderer.invoke('export-history', format), + saveTextFile: (defaultName, content, filters) => ipcRenderer.invoke('save-text-file', defaultName, content, filters), // Hoster settings getHosterSettings: () => ipcRenderer.invoke('get-hoster-settings'), @@ -100,6 +101,9 @@ contextBridge.exposeInMainWorld('api', { onShutdownCountdown: (callback) => { ipcRenderer.on('shutdown-countdown', (_event, data) => callback(data)); }, + onUploadLogFallback: (callback) => { + ipcRenderer.on('upload-log-fallback', (_event, data) => callback(data)); + }, // Remote Control remoteGetSettings: () => ipcRenderer.invoke('remote:get-settings'), remoteSaveSettings: (settings) => ipcRenderer.invoke('remote:save-settings', settings), diff --git a/renderer/app.js b/renderer/app.js index 1c095dd..c32480f 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -104,6 +104,9 @@ async function init() { handleStats(data); }); window.api.onShutdownCountdown(handleShutdownCountdown); + window.api.onUploadLogFallback((data) => { + alert('Der konfigurierte Log-Pfad konnte nicht beschrieben werden.\n\nNeue Einträge werden zwischenzeitlich hier gespeichert:\n' + (data && data.fallbackPath ? data.fallbackPath : '(Fallback)') + '\n\nBitte in den Einstellungen einen gültigen Pfad setzen.'); + }); // Folder monitor: auto-queue new files window.api.onFolderMonitorNewFiles((files) => { @@ -1122,6 +1125,33 @@ function clearAllRecentFiles() { renderRecentUploadsPanel(); } +async function exportAllRecentFiles() { + if (sessionFilesData.length === 0) { + alert('Keine Einträge zum Exportieren.'); + return; + } + const rows = sortRecentFiles(sessionFilesData); + const header = 'timestamp|hoster|link|filename|status'; + const lines = rows.map(r => { + const ts = r.timestamp || r.time || ''; + const host = r.host || r.hoster || ''; + const link = r.link || ''; + const name = r.filename || ''; + const status = r.isError ? 'error' : 'ok'; + return [ts, host, link, name, status].map(v => String(v).replace(/[\r\n|]/g, ' ')).join('|'); + }); + const content = [header, ...lines].join('\n') + '\n'; + const defaultName = `uploads-${new Date().toISOString().slice(0, 10)}.log`; + try { + const result = await window.api.saveTextFile(defaultName, content, [ + { name: 'Log-Datei', extensions: ['log', 'txt', 'csv'] } + ]); + if (result && result.ok) showCopyToast(`${rows.length} Einträge exportiert`); + } catch (err) { + alert('Export fehlgeschlagen: ' + (err.message || err)); + } +} + function copySelectedRecentLinks() { const links = sessionFilesData .filter(r => selectedRecentIds.has(r.order) && !r.isError) @@ -3241,6 +3271,7 @@ function setupListeners() { document.getElementById('accountsRunHealthCheckBtn').addEventListener('click', () => runHealthCheck('manual')); document.getElementById('copyAllLinksBtn').addEventListener('click', copyAllLinks); document.getElementById('clearRecentFilesBtn').addEventListener('click', clearAllRecentFiles); + document.getElementById('exportRecentFilesBtn').addEventListener('click', exportAllRecentFiles); document.getElementById('retryFailedBtn').addEventListener('click', () => { queueJobs.forEach(j => { if (j.status === 'error') selectedJobIds.add(j.id); }); retrySelectedJobs(); diff --git a/renderer/index.html b/renderer/index.html index a84766e..e41b7cc 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -105,6 +105,7 @@ Zuletzt erzeugte Upload-Links +