From 655fb6230b736959b8ee31608389ae5492d97659 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 19 Apr 2026 23:04:20 +0200 Subject: [PATCH] feat(rotation): fast-fail on account-specific errors + open-log-folder button + sync rot-log flush MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three related improvements that landed together while wiring up the rotation log infrastructure: - Fast-fail classifier: errors that clearly indicate the account itself is the problem (rate limit, quota, banned/suspended, auth failure, 401/403, 'Kein Upload-Server' from delivery-node etc.) now skip the remaining retries and go straight to rotation. No more waiting 5 × 3s between retries just to end up rotating anyway. Emits a 'fast-fail' rot-log event so the shortcut is visible. - Settings: 'Öffnen' button next to the log-file-path input reveals the active log file (or its directory if nothing's written yet) in the OS file manager, so users don't have to remember paths. - rotLog() writes the rotation log synchronously. Only a handful of events fire per batch; the 500ms flush batching was saving nothing and made the file look empty when users checked right after an event. (The main debug log still uses the batched async path — that one is high-volume.) --- lib/upload-manager.js | 37 +++++++++++++++++++++++++++++++++++++ main.js | 39 +++++++++++++++++++++++++++++++++++---- preload.js | 1 + renderer/app.js | 2 ++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/lib/upload-manager.js b/lib/upload-manager.js index e85269f..eb376a6 100644 --- a/lib/upload-manager.js +++ b/lib/upload-manager.js @@ -56,6 +56,34 @@ class UploadManager extends EventEmitter { this.emit('rot-log', { ts: Date.now(), event, ...data }); } + // Error classes that mean "this account is the problem, retrying on it won't + // help" — we skip the remaining retries and go straight to the fallback + // account. Keeps single runs fast when an account is rate-limited, banned, + // or out of quota. Transient network issues still go through the normal + // retry loop on the same account. + _shouldSkipRetryOnAccountError(err) { + if (!err || !err.message) return false; + const m = String(err.message); + const PATTERNS = [ + /Kein Upload-Server/i, + /No upload server/i, + /kein server/i, + /quota/i, + /limit (reached|exceeded|überschritten)/i, + /rate[- ]?limit/i, + /too many requests/i, + /\b(401|403)\b/, + /Falscher (User|Username|Passwort)/i, + /Incorrect (Login|Password)/i, + /invalid (credentials|api[- ]?key|token|session)/i, + /(account|user) (banned|suspended|disabled|gesperrt)/i, + /not authorized/i, + /forbidden/i, + /session (expired|abgelaufen)/i + ]; + return PATTERNS.some(p => p.test(m)); + } + updateSettings(hosterSettings, globalSettings) { this.hosterSettings = hosterSettings || this.hosterSettings; this.globalSettings = globalSettings || this.globalSettings; @@ -469,6 +497,15 @@ class UploadManager extends EventEmitter { } lastError = err; + // Account-specific errors — don't waste retries on the same account, + // jump straight to rotation. + if (this._shouldSkipRetryOnAccountError(err)) { + this._rotLog('fast-fail', { + hoster: task.hoster, fileName, accountId: task.accountId, + attempt, error: err && err.message ? err.message : String(err) + }); + break; + } if (attempt >= maxAttempts) break; // Wait 3 seconds before retry await this._sleep(3000, signal); diff --git a/main.js b/main.js index 6b1f421..10656af 100644 --- a/main.js +++ b/main.js @@ -108,12 +108,24 @@ function rotLog(msg, ts) { try { const iso = new Date(ts || Date.now()).toISOString(); const line = `[${iso}] ${msg}\n`; - _rotLogBuffer.push(line); + // Write synchronously. Rotation events are rare (a handful per batch) so + // the batching optimization from debugLog doesn't buy us anything, and + // syncing guarantees the user can refresh the file and see fresh entries + // without waiting on a flush timer. + const candidates = [ + getRotLogPath(), + path.join(app.getPath('desktop') || app.getPath('userData'), 'account-rotation.log'), + path.join(app.getPath('userData'), 'account-rotation.log') + ]; + for (const target of candidates) { + try { + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.appendFileSync(target, line, 'utf-8'); + break; + } catch {} + } // Mirror into the main debug log for single-file-grep convenience. _debugLogBuffer.push(`[${iso}] [ROT] ${msg}\n`); - if (!_rotLogFlushTimer) { - _rotLogFlushTimer = setTimeout(() => { _rotLogFlushTimer = null; _flushRotLog(); }, 500); - } if (!_debugLogFlushTimer) { _debugLogFlushTimer = setTimeout(() => { _debugLogFlushTimer = null; _flushDebugLog(); }, 500); } @@ -1189,6 +1201,25 @@ ipcMain.handle('finish-after-active', () => { return true; }); +ipcMain.handle('open-log-folder', async () => { + // Reveal the active log file (or its directory) in the OS file manager. + // Prefers the configured log path, then the rotation log, then just the + // parent dir. + const { shell } = require('electron'); + const primary = getLogFilePath(); + if (fs.existsSync(primary)) { shell.showItemInFolder(primary); return { ok: true, path: primary }; } + const rot = getRotLogPath(); + if (fs.existsSync(rot)) { shell.showItemInFolder(rot); return { ok: true, path: rot }; } + try { + const dir = path.dirname(primary); + fs.mkdirSync(dir, { recursive: true }); + shell.openPath(dir); + return { ok: true, path: dir }; + } catch (err) { + return { ok: false, error: err.message }; + } +}); + ipcMain.handle('clear-history', async () => { await configStore.clearHistory(); return true; diff --git a/preload.js b/preload.js index b0d4d54..3ec208f 100644 --- a/preload.js +++ b/preload.js @@ -107,6 +107,7 @@ contextBridge.exposeInMainWorld('api', { onAccountRotationLog: (callback) => { ipcRenderer.on('account-rotation-log', (_event, data) => callback(data)); }, + openLogFolder: () => ipcRenderer.invoke('open-log-folder'), // 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 a635331..8b2879a 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -2291,6 +2291,7 @@ function renderSettings() { +
@@ -2584,6 +2585,7 @@ function renderSettings() { } document.getElementById('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath); + document.getElementById('openLogFolderBtn')?.addEventListener('click', () => window.api.openLogFolder()); document.getElementById('manualUpdateCheckBtn')?.addEventListener('click', async (e) => { const btn = e.target; btn.disabled = true;