feat(log): per-hoster toggle for writing links to fileuploader.log
New per-hoster setting "Links in Log schreiben" (logToFile, default on). When unchecked for a hoster, that hoster's successful upload links are no longer written to fileuploader.log — other hosters keep logging independently. - lib/config-store.js: logToFile: true added to HOSTER_SETTINGS_DEFAULTS; merge-on-load gives every hoster the key (old configs included). - renderer/app.js: checkbox per hoster panel + collection loop now handles type=checkbox (boolean) alongside the numeric fields. The autosave bind already special-cased checkboxes (change event). - lib/log-policy.js (new): hosterLogToFileEnabled() — pure, opt-out semantics. Only an explicit logToFile===false disables; missing/ malformed/non-true values all default ON so links are never silently dropped. - main.js: shouldLogHosterToFile() reads the LIVE uploadManager .hosterSettings (so a mid-batch toggle takes effect at once), falls back to persisted config, then to enabled. Guards appendUploadLog in the done handler; skipped writes get a debugLog line. Tests: 8 log-policy (defaults, opt-out, per-hoster independence, malformed input) + 2 config-store (default true, persisted false survives reload). 147/147 green, eslint clean.
This commit is contained in:
parent
4c88c0a756
commit
57f8f0876e
@ -8,7 +8,8 @@ const HOSTER_SETTINGS_DEFAULTS = {
|
||||
parallelCount: 2, // 1-100
|
||||
restartBelowKbs: 0, // 0 = off
|
||||
timeIntervalSec: 0, // delay between jobs
|
||||
maxSizeMb: 0 // 0 = unlimited
|
||||
maxSizeMb: 0, // 0 = unlimited
|
||||
logToFile: true // write this hoster's successful links to fileuploader.log
|
||||
};
|
||||
|
||||
// Template for each hoster type (used as defaults for new accounts)
|
||||
|
||||
17
lib/log-policy.js
Normal file
17
lib/log-policy.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Per-hoster upload-log policy. Decides whether a hoster's successful upload
|
||||
// links get written to fileuploader.log. Pure + dependency-free so it's
|
||||
// trivially unit-testable and shared between the runtime decision and tests.
|
||||
//
|
||||
// Contract: logging is ON unless the hoster's settings explicitly set
|
||||
// logToFile === false. Missing settings / missing hoster / malformed input
|
||||
// all default to ON, so the feature is strictly opt-out and never silently
|
||||
// drops links because a config key wasn't present.
|
||||
|
||||
function hosterLogToFileEnabled(hosterSettings, hoster) {
|
||||
if (!hosterSettings || typeof hosterSettings !== 'object') return true;
|
||||
const hs = hosterSettings[hoster];
|
||||
if (!hs || typeof hs !== 'object') return true;
|
||||
return hs.logToFile !== false;
|
||||
}
|
||||
|
||||
module.exports = { hosterLogToFileEnabled };
|
||||
26
main.js
26
main.js
@ -14,6 +14,7 @@ const backupCrypto = require('./lib/backup-crypto');
|
||||
const FolderMonitor = require('./lib/folder-monitor');
|
||||
const RemoteServer = require('./lib/remote-server');
|
||||
const { maybeRotateLogFile } = require('./lib/log-rotation');
|
||||
const { hosterLogToFileEnabled } = require('./lib/log-policy');
|
||||
|
||||
let mainWindow;
|
||||
let _lastImportPath = null;
|
||||
@ -416,6 +417,20 @@ function _persistFallbackLogPath(workingPath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Whether this hoster's successful links should land in fileuploader.log.
|
||||
// Reads the LIVE uploadManager.hosterSettings (kept current via
|
||||
// updateSettings) so a mid-batch toggle takes effect immediately. Falls back
|
||||
// to the persisted config if no batch is active, then defaults to enabled.
|
||||
function shouldLogHosterToFile(hoster) {
|
||||
const live = uploadManager && uploadManager.hosterSettings ? uploadManager.hosterSettings : null;
|
||||
if (live) return hosterLogToFileEnabled(live, hoster);
|
||||
try {
|
||||
return hosterLogToFileEnabled(configStore.load().hosterSettings, hoster);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function appendUploadLog(hoster, link, fileName) {
|
||||
const now = new Date();
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
@ -1238,11 +1253,18 @@ ipcMain.handle('start-upload', (_event, payload) => {
|
||||
error: data.error || null, attempt: data.attempt || 0, maxAttempts: data.maxAttempts || 0
|
||||
});
|
||||
}
|
||||
// Write to fileuploader.log immediately when a single upload finishes
|
||||
// Write to fileuploader.log immediately when a single upload finishes —
|
||||
// unless the user disabled logging for this hoster (per-hoster toggle).
|
||||
// Read from the live uploadManager.hosterSettings so a mid-batch toggle
|
||||
// (which calls updateSettings) takes effect immediately.
|
||||
if (data.status === 'done' && data.result) {
|
||||
const link = data.result.download_url || data.result.embed_url || data.result.file_code || '';
|
||||
if (link) {
|
||||
appendUploadLog(data.hoster || '', link, data.fileName || '');
|
||||
if (shouldLogHosterToFile(data.hoster)) {
|
||||
appendUploadLog(data.hoster || '', link, data.fileName || '');
|
||||
} else {
|
||||
debugLog(`upload-log: skip ${data.fileName} @ ${data.hoster} (logToFile disabled for hoster)`);
|
||||
}
|
||||
} else {
|
||||
debugLog(`WARNING: done but no link for ${data.fileName} @ ${data.hoster}: ${JSON.stringify(data.result)}`);
|
||||
}
|
||||
|
||||
@ -2826,6 +2826,11 @@ function renderSettings() {
|
||||
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="maxSizeMb" value="${hs.maxSizeMb ?? 0}" min="0">
|
||||
<span class="hint">0 = unbegrenzt</span>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Links in Log schreiben</label>
|
||||
<input type="checkbox" class="hs-input settings-autosave" data-hoster="${name}" data-hs="logToFile" ${hs.logToFile !== false ? 'checked' : ''}>
|
||||
<span class="hint">Erfolgreiche Links dieses Hosters in fileuploader.log</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -2939,7 +2944,8 @@ async function saveSettings(options = {}) {
|
||||
const hs = { ...(hosterSettings[name] || {}) };
|
||||
document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => {
|
||||
const field = input.dataset.hs;
|
||||
if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
|
||||
if (input.type === 'checkbox') hs[field] = input.checked;
|
||||
else if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
|
||||
else hs[field] = parseInt(input.value, 10) || 0;
|
||||
});
|
||||
newHosterSettings[name] = hs;
|
||||
|
||||
@ -1,51 +1,22 @@
|
||||
# Verbesserungs-Loop — open items
|
||||
# Feature: Per-Hoster Toggle "Links in fileuploader.log schreiben"
|
||||
|
||||
## Released
|
||||
- ✅ 3.3.0 — Performance-Fixes (queue-cap, sort-throttle, history-delegation, recent-cap) + Log-Recovery
|
||||
- ✅ 3.3.1 — `removeFromQueueOnDone` coalesced via microtask (kein O(N²) mehr bei done-Bursts)
|
||||
- ✅ 3.3.2 — `fileuploader.log` Auto-Rotation bei 50 MB (max 3 Backups: .1 .2 .3)
|
||||
- ✅ 3.3.3 — `_jobLogCollector` Cap auf 1000 tracked jobs (FIFO-eviction beim Überschreiten)
|
||||
- ✅ 3.3.4 — `applyQueueSelectionClasses` + `applyRecentSelectionClasses` nutzen `getElementsByClassName` (live HTMLCollection statt querySelectorAll re-query bei jedem Klick)
|
||||
- ✅ 3.3.5 — Log-Rotation extrahiert nach `lib/log-rotation.js` + 10 neue Unit-Tests (cap, shift, eviction, idempotency, maxBackups=1, invalid input, no-extension)
|
||||
- ✅ 3.3.6 — CSS `.queue-row` transition nur noch auf `:hover` (kein 150ms compositor-tween bei status-flips)
|
||||
- ✅ 3.3.7 — `_sessionTrackedJobs`/`_sessionDoneJobs` werden bei handleBatchDone gegen current queueJobs geprunt (no more unbounded session memory growth across batches)
|
||||
- ✅ 3.3.8 — queue-cap-prune-Logik nach `lib/queue-prune.js` extrahiert (dual-environment: Node + Browser-global) + 10 Unit-Tests (insertion-order, limit=0, malformed entries, large-queue 5000-job sweep)
|
||||
- ✅ 3.3.9 — Throttled-Cache nach `lib/throttled-cache.js` extrahiert (von sortQueueJobs dynamic-throttle genutzt) + 12 Unit-Tests (TTL-Boundary, identity-tracking, fake-clock, peek/clear, refreshMs=0, large-input)
|
||||
- ✅ 3.3.10 — `npm audit fix` (non-breaking): 4 vulnerabilities geschlossen (16 → 12), nur Lock-file Update
|
||||
- ✅ 3.3.11 — Patch-Bumps `eslint 10.1→10.2`, `undici 7.24→7.25`, `ws 8.19→8.20` (semver-compatible)
|
||||
- ✅ 3.3.12 — Race condition fix: `uploadManager = null` in batch-done clobberte einen frisch gespawnten Manager wenn user mid-await neuen batch startete (deep-audit finding HIGH-1)
|
||||
- ✅ 3.3.13 — `save-global-settings-sync` reportet jetzt `returnValue=false` bei Fehlern + debugLog statt silent swallow; TOCTOU bei .bak-Refresh in beiden Pfaden (main.js + lib/config-store.js _atomicWrite) entkoppelt: bak-Read-Failure failt nicht mehr den ganzen Save (deep-audit findings HIGH-2 + MED-4)
|
||||
- ✅ 3.3.14 — Parser-null-payload guard: `uploadFile` normalisiert payload zu `{}` falls `JSON.parse('null')` o.ä.; `parseDoodstreamResult` + `parseByseResult` haben defensive guards für direct callers + 7 neue Unit-Tests (null/non-object, malformed entries, fileRejected/accountError flips, valid filecode happy path)
|
||||
- ✅ 3.3.15 — Cancellation latency fix: nach `_sleep(800)` in der rotation-while-loop wird `signal.aborted`/`stopAfterActive` re-checkt bevor das ganze override-resolution-Setup läuft (deep-audit MED-5)
|
||||
- ✅ 3.3.16 — Auto-Rotation für die anderen 3 internen Logs (`upload-debug.log` 25 MB, `account-rotation.log` 10 MB, `doodstream-debug.log` 10 MB), je 2 Backups — alle nutzen `lib/log-rotation.js` (zuvor nur `fileuploader.log` rotiert)
|
||||
- ✅ 3.3.17 — `npm audit fix --force` (User authorized): `electron-builder 25 → 26`, `electron 33 → 41`, alle 12 verbleibenden Vulns geschlossen (12 → 0). Build verifiziert (NSIS+portable laufen mit electron 41), 126/126 grün.
|
||||
- ✅ 3.3.18 — Microtask-Coalescer extrahiert nach `lib/coalesced-set.js` mit injectable scheduler (für Tests) + 11 Unit-Tests (single/multi-add coalesce, dedup, sequential batches, drainSync vs scheduler-noop, throwing-apply-recovery, 5000-burst). 137/137 grün.
|
||||
## Goal
|
||||
Pro Hoster ein-/ausschaltbar machen ob dessen erfolgreiche Upload-Links in die fileuploader.log geschrieben werden.
|
||||
|
||||
## Open items (priorisiert)
|
||||
## Plan
|
||||
- [x] `lib/config-store.js` — `logToFile: true` zu `HOSTER_SETTINGS_DEFAULTS` (default an).
|
||||
- [x] `renderer/app.js renderSettings` — Checkbox "Links in Log schreiben" pro Hoster-Panel (`data-hs="logToFile"`, type=checkbox).
|
||||
- [x] `renderer/app.js saveSettings` — collection-loop erweitert: checkbox → boolean.
|
||||
- [x] `lib/log-policy.js` (neu, testbar) — `hosterLogToFileEnabled(hosterSettings, hoster)`, opt-out semantics.
|
||||
- [x] `main.js` — `shouldLogHosterToFile(hoster)` liest live uploadManager.hosterSettings, fallback configStore, dann default true. Guard vor appendUploadLog im done-handler.
|
||||
- [x] Tests: 8 log-policy + 2 config-store (default true, persist false). 147/147 grün.
|
||||
- [x] ESLint clean. Backup-Import robust (default-true bei fehlendem key).
|
||||
|
||||
(alle stabilitäts-items aus deep-audit erledigt)
|
||||
## Verifikation
|
||||
- logToFile default true → bestehendes Verhalten unverändert für alle die's nicht togglen.
|
||||
- Toggle off für Hoster X → uploads von X werden NICHT geloggt, andere Hoster weiter schon.
|
||||
- Live-Wirkung: `uploadManager.hosterSettings` wird via updateSettings aktualisiert → greift auch mid-batch nach save.
|
||||
|
||||
### Code-Qualität
|
||||
(alle erledigt)
|
||||
|
||||
### Loop-Status
|
||||
Alle initial im 3.3.0-Audit identifizierten Items sind nun adressiert. Beide verbliebenen open items sind explizit deferred (microtask-fake-timer-Setup ist Refactor, audit-fix --force ist Major-Bump und braucht User-OK).
|
||||
|
||||
**Iteration 11 + 18 (skipped, no release)**: kein nicht-deferred Item übrig. Loop läuft idle weiter — bei nächstem Cron-Tick prüft er erneut, falls inzwischen neue Issues aufgetaucht sind.
|
||||
|
||||
**Bilanz nach 16 produktiven Releases (3.3.0 → 3.3.16)**:
|
||||
- 8 Stabilitäts-Fixes (race conditions, error swallowing, parser crashes, cancellation latency, log rotation, queue session-memory)
|
||||
- 5 Performance-Fixes (queue-cap, sort-throttle, history-delegation, recent-cap, removeFromQueueOnDone coalesce)
|
||||
- 4 Test-Coverage-Erweiterungen (+39 Unit-Tests: 87 → 126)
|
||||
- 3 Code-Quality-Bumps (CSS-scope, npm-audit-fix, dep patches)
|
||||
- 3 Modul-Extractions (log-rotation, queue-prune, throttled-cache)
|
||||
|
||||
Sinnvolle nächste Schritte für den User:
|
||||
- "Loop stop" wenn nichts mehr passieren soll (CronDelete `01e33ae1`)
|
||||
- "Major bump genehmigt" für `npm audit fix --force` (closes 12 deferred vulns, bumpt electron-builder@26)
|
||||
- Neue konkrete User-Beschwerden / Bug-Reports
|
||||
- Manuelle Anweisung was als nächstes interessant wäre
|
||||
|
||||
## Loop-Notes
|
||||
- Cron-Job `01e33ae1` läuft alle 30min (:07/:37), Session-only.
|
||||
- Pro Iteration: GENAU EIN Issue. Auto-Release bei grünen Tests. Boundary: keine Features, keine Major-Refactors.
|
||||
## Seiteneffekte zu prüfen
|
||||
- Backup-Import/Export: hosterSettings inkl. logToFile mitnehmen (sollte automatisch da generisches Objekt).
|
||||
- Settings-autosave (checkbox change-event ist bereits gehandhabt in der bind-loop).
|
||||
|
||||
@ -80,6 +80,21 @@ describe('ConfigStore', () => {
|
||||
assert.equal(config.hosterSettings['voe.sx'].retries, 5);
|
||||
assert.equal(config.hosterSettings['voe.sx'].parallelCount, 2); // default
|
||||
assert.equal(config.hosterSettings['voe.sx'].maxSpeedKbs, 0); // default
|
||||
assert.equal(config.hosterSettings['voe.sx'].logToFile, true); // default on
|
||||
});
|
||||
|
||||
it('logToFile defaults to true for every hoster', () => {
|
||||
const config = store.load();
|
||||
for (const name of ['doodstream.com', 'voe.sx', 'vidmoly.me', 'byse.sx', 'clouddrop.cc']) {
|
||||
assert.equal(config.hosterSettings[name].logToFile, true, `${name} should default logToFile=true`);
|
||||
}
|
||||
});
|
||||
|
||||
it('logToFile=false persists and survives reload', async () => {
|
||||
await store.save({ hosterSettings: { 'voe.sx': { logToFile: false } } });
|
||||
const config = store.load();
|
||||
assert.equal(config.hosterSettings['voe.sx'].logToFile, false, 'explicit false preserved');
|
||||
assert.equal(config.hosterSettings['byse.sx'].logToFile, true, 'other hoster still defaults on');
|
||||
});
|
||||
|
||||
it('save only updates provided sections', async () => {
|
||||
|
||||
52
tests/log-policy.test.js
Normal file
52
tests/log-policy.test.js
Normal file
@ -0,0 +1,52 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { hosterLogToFileEnabled } = require('../lib/log-policy');
|
||||
|
||||
test('enabled by default when settings missing entirely', () => {
|
||||
assert.equal(hosterLogToFileEnabled(null, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled(undefined, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled('not-an-object', 'voe.sx'), true);
|
||||
});
|
||||
|
||||
test('enabled when hoster has no settings entry', () => {
|
||||
assert.equal(hosterLogToFileEnabled({}, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled({ 'byse.sx': { logToFile: false } }, 'voe.sx'), true);
|
||||
});
|
||||
|
||||
test('enabled when hoster entry has no logToFile key (back-compat with old configs)', () => {
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { retries: 3 } }, 'voe.sx'), true);
|
||||
});
|
||||
|
||||
test('enabled when logToFile is explicitly true', () => {
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { logToFile: true } }, 'voe.sx'), true);
|
||||
});
|
||||
|
||||
test('DISABLED only when logToFile is explicitly false', () => {
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { logToFile: false } }, 'voe.sx'), false);
|
||||
});
|
||||
|
||||
test('truthy-but-not-true values do not accidentally disable', () => {
|
||||
// Only the strict boolean false disables — guards against e.g. a stored 0/""
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { logToFile: 0 } }, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { logToFile: '' } }, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { logToFile: null } }, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': { logToFile: undefined } }, 'voe.sx'), true);
|
||||
});
|
||||
|
||||
test('per-hoster independence: one off, others on', () => {
|
||||
const settings = {
|
||||
'voe.sx': { logToFile: false },
|
||||
'byse.sx': { logToFile: true },
|
||||
'doodstream.com': { retries: 3 }
|
||||
};
|
||||
assert.equal(hosterLogToFileEnabled(settings, 'voe.sx'), false);
|
||||
assert.equal(hosterLogToFileEnabled(settings, 'byse.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled(settings, 'doodstream.com'), true);
|
||||
assert.equal(hosterLogToFileEnabled(settings, 'clouddrop.cc'), true); // not present → on
|
||||
});
|
||||
|
||||
test('malformed hoster entry (string/number) defaults to on', () => {
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': 'broken' }, 'voe.sx'), true);
|
||||
assert.equal(hosterLogToFileEnabled({ 'voe.sx': 42 }, 'voe.sx'), true);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user