Before: a non-transient / non-file-rejected / non-account-specific
error (e.g. "VOE Upload: <any generic message>") would burn the full
retries-per-account budget on the primary before the rotation logic
even kicked in. On retries=5 that's "Retry 2/5 Primär", "Retry 3/5
Primär", … all on the same broken account before the fallback gets
a shot.
Now:
- main.js pre-resolves the next fallback for every hoster at batch-
start (stored in _accountOverrides via the existing session cache +
primeOverrides path). Pre-job-swap still ignores it until the
primary is actually marked failed, so jobs still begin on primary.
- upload-manager.js: in the retry loop's generic error branch,
_hasPendingOverride() checks whether a usable fallback is ready.
If yes and the error is NOT transient (transient = network glitch
= retry same acc), break out to rotation. Marks primary failed,
rotates to acc2, retries there.
- Result: for a 2-account hoster, worst case is 1 attempt on primary
+ retries-per-account on fallback, instead of N × 2. Transient
network errors (ENOTFOUND / ECONNRESET / socket hang up) keep the
old "retry same account" semantics because the network is the
issue, not the account.
- Single-account hosters: unchanged. No pending override = classic
retry-on-same-account until exhausted.
3 new tests pin: generic + override → rotate on attempt 1; transient
+ override → stay on same acc; no override → classic retry. 87/87
green.
Two related visibility improvements.
1. Status cell now shows which account the job is running on:
"Upload · Primär", "Retry 2/3 · Fallback #1: <error>", etc.
- _emitProgress passes task.accountId in every progress event
- renderer maps accountId → position in config.hosters[hoster] and
renders "Primär" for index 0 and "Fallback #N" for the rest
- Applies to uploading/getting-server/retrying (static states like
done/error already tell their own story)
2. Right-click on a job → "Log anzeigen" opens a modal with the full
per-job trail: every rot-log entry tagged with that job's jobId
plus every non-uploading progress transition. Replaces the need to
grep account-rotation.log for a single filename.
- UploadManager: all 13 job-scoped _rotLog calls now carry jobId
- main.js: _jobLogCollector Map<jobId, Array<entry>> with 200-entry
ring buffer per job; cleared on each new start-upload (fresh
batch = fresh log). addJobs mid-batch keeps history.
- New IPC 'get-job-log' returns the array; preload.js exposes
window.api.getJobLog(jobId)
- renderer: modal card + context-menu item "Log anzeigen";
entries formatted as "[HH:MM:SS.mmm] [event] k=v k=v"; copy-to-
clipboard button
Previously: clicking "Erneut versuchen" after a batch had already
finished spawned a fresh UploadManager with empty _failedAccounts and
_accountOverrides. The first retry then burned the full retry budget
on the account we already knew was dead (e.g. disk-space-full byse
account) before rotation kicked in again — same problem we fixed for
within-batch flow but for across-batch flow.
- main.js: two module-level maps (_sessionFailedAccounts,
_sessionAccountOverrides) cache rotation state across batches in the
same app session. Populated on account-failed and on both
switchAccount paths (event-driven + save-config re-resolve).
- lib/upload-manager.js: startBatch(tasks, opts) accepts
primeFailedAccounts + primeOverrides. State is still cleared first
(legacy behaviour for callers without opts), then re-primed from the
passed session state. batch-start rot-log entry reports how many
entries were primed for diagnostics.
- Tests: prime priority is honored (pre-job-swap fires on first
attempt, no fast-fail, no upload to acc1); back-compat for callers
that don't pass opts.
App restart remains the reset signal — matches the "neuer Tag, acc1
hat vielleicht wieder Platz" expectation.
Three small, unrelated reliability improvements bundled:
1. lib/hosters.js (_resolveByseUploadByName): drop the "only one new
file → claim it" fallback. Under parallel byse uploads, job A's
poller could claim job B's newly-uploaded file and return the wrong
URL. Now requires exact normalized name match. Trade-off: a few
false negatives if byse rewrites the filename beyond our
normalizer, but parallel correctness wins.
2. tests/upload-manager.test.js: pin the transient-network classifier
behaviour with 2 new tests covering common transient strings
(ENOTFOUND, ECONNRESET, socket hang up, fetch failed, EAI_AGAIN…)
and verifying real account-level / file-rejected errors are NOT
misclassified as transient. Baseline stays clean: 82/82 green.
3. main.js: log process.memoryUsage() snapshot at batch-start and
batch-done. One line each — harmless in the happy path, gives us
the data points needed to spot long-session RSS/heap growth across
batches without DevTools instrumentation.
At 500+ queued jobs these lines bloated upload-debug.log with
megabyte-sized entries per batch-start and added visible latency to
the IPC handler. Log sizes only.
Three related gaps closed so one full byse account stops wasting
attempts on every subsequent job and later-added accounts get picked
up without an app restart.
1. Pre-job-swap moved BEHIND the semaphore acquire. At scale (500 jobs
/ 1 slot) every worker was checking _failedAccounts at spawn time
before the first upload had even tried — so none of them saw the
failed state. Now each worker re-checks right before its first
upload attempt.
2. save-config IPC handler re-resolves fallbacks for any account that
is already in _failedAccounts but has no override set. Previously
account-failed only fired once per account, so a config change
after the first mark-failed was silently ignored and the batch
stayed stuck on the dead account until the app restarted.
3. UploadManager exposes getFailedAccountKeys() and getOverride(hoster)
so main.js can drive the late re-resolve without poking private
fields.
4 new tests: pre-job-swap after semaphore, getters contract, fresh
manager resets learned state, late-added fallback is honored by
subsequent jobs. 80/80 green.
When the configured log path isn't writable and we fall back to
Desktop/userData, the working fallback now gets saved into
globalSettings.logFilePath automatically. Benefits:
- Next session writes directly to the known-working path, no
fallback ladder, no recurring toast warning.
- The Settings input reflects the actual path in use, so users
don't stay confused about where their uploads are being logged.
- Live update via IPC — if the Settings view is currently open,
the input value updates without needing a view switch.
Daily-log mode is handled: we strip the -YYYY-MM-DD suffix before
persisting so tomorrow's auto-rotation doesn't double-date the
filename.
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.)
To trace whether the fallback chain actually engages during real uploads,
every rotation decision now emits a structured 'rot-log' event from the
upload-manager. main.js persists each event to a new account-rotation.log
(same directory as fileuploader.log; falls back to Desktop then userData)
and also mirrors it into the main debug log with a [ROT] prefix for
single-file grepping.
Logged events:
- batch-start (clears _failedAccounts / _accountOverrides)
- pre-job-swap / pre-job-swap-blocked (job picks override before first try)
- retries-exhausted / mark-failed (enters rotation loop)
- rotate (switched to new account, retry starting)
- rotation-end (no override / override already failed)
- final-error (all accounts exhausted)
- switchAccount (main resolved the next fallback)
The renderer shows a toast on 'rotate', 'rotation-end' and 'final-error'
so fallback behavior is visible live instead of buried in logs.
Last round of targeted wins:
- upload-manager progress callback was allocating a fresh
{ jobId, speedKbs, bytesUploaded } object on every fs stream chunk
(hundreds of times per second per active job). Now a single entry
is created at job start and mutated in place — zero allocations
on the steady-state progress tick.
- upload-manager stats timer's two separate activeJobs.values()
scans (globalSpeedKbs + inProgressBytes) merged into one pass.
- clouddrop-upload.js reuses a single Buffer.allocUnsafe(chunkSize)
across all chunks, taking subarray() only for the tail chunk.
A 1 GB upload no longer allocates 64× 16 MB = 1 GB of short-lived
buffers — real GC relief during many-file batches.
- _resolveUploadLogTarget is now cached; the fallback ladder runs
once per session (or when the user changes the log path / daily-log
date rolls), not on every 500ms flush.
- renderRecentUploadsPanel skips updateRecentSortHeaders on the
append-only fast path — sort state hasn't changed, headers don't
need recomputing.
Four more wins targeting batch-heavy paths:
- updateQueueActionButtons replaced three O(n) queueJobs.some() scans
with a single O(|selection|) pass over selectedJobIds, using the
existing _jobIndexById map. Selection change cost on a 1000-job
queue drops from ~3000 comparisons to |selection|.
- applySummaryResults built a (fileName+hoster)→job Map once per call
instead of running queueJobs.find() per result. Big batches
(hundreds of files × multiple hosters) no longer scale O(n²).
- addPathsToQueue and the folder-monitor auto-queue path built their
dedup Set up front instead of running .find() per incoming path.
Picking a folder with thousands of files now dedups in O(n+m)
instead of O(n×m).
- appendUploadLog became async + buffered like debugLog. A burst of
20 files completing within a second becomes one fs.appendFile
instead of 20 fs.appendFileSync that each blocked the main event
loop. Fallback ladder (primary → Desktop → userData) is preserved;
pending buffer flushes synchronously on before-quit.
Three more rounds of lag removal aimed at heavy upload sessions:
- main-process debugLog() was doing fs.appendFileSync on every call
and was firing hundreds of times per second during busy uploads
(progress transitions, unhandled rejection traces, folder-monitor
events). Replaced with an in-memory buffer flushed every 500ms via
async appendFile — the main event loop is no longer blocked per
line. Buffered entries flush synchronously on before-quit.
- the renderer's 'RX upload-progress' / 'RX upload-stats' listeners
were emitting one IPC roundtrip per event. For 20 concurrent jobs
that's 80 IPC messages/sec just for logging. They now skip the
debug call on the hot 'uploading' tick and only log transitions.
- _onQueueScroll now coalesces scroll events via requestAnimationFrame
so a fast trackpad fling triggers one virtual render per frame
instead of one per wheel event.
- maybeAddSessionFile switched from O(n) sessionFilesData.some() dedup
to an O(1) Set lookup keyed on (link, filename, host). Adding 1000
results to an already-populated panel drops from ~500ms to <5ms.
Two issues:
1. Verlauf-Export CSV put the opaque file_code in the Link column when
the upload had no real URL, so the column looked like just a bunch
of IDs. Now only real http(s) URLs land in that column.
2. Hoster passwords and API keys were stored as plaintext in
electron-config.json. Now wrapped with Electron's safeStorage (DPAPI
on Windows, Keychain on macOS, libsecret on Linux) and stored as
'enc:v1:<base64>'.
Credentials are decrypted on load so in-memory flows stay unchanged,
and backups still export plaintext inside the existing .mhu envelope
so they remain portable between machines/users. Legacy plaintext
configs auto-migrate on next write.
If the configured log path (or the default exe-adjacent path) isn't
writable, we now try the current user's Desktop first — that's where
users actually look — and only fall back to AppData if Desktop is
also unavailable. The daily-log filename suffix is preserved on the
fallback file so the format stays consistent.
A backup made on 'Server A' carries absolute paths (logFilePath,
folderMonitor.folderPath, pendingQueue file paths) that do not exist
on 'Server B' — leading to silent log-write failures, folder-monitor
start errors on missing directories, and queue jobs pointing at
non-existent files.
On import, now:
- clear logFilePath if its parent directory doesn't exist here
- clear folderMonitor.folderPath + disable it if the directory is missing
- clear pendingQueue (queue state is inherently per-machine)
Also harden startup: folder-monitor auto-start now verifies the path
exists and persists enabled=false if not, so one missing-path launch
doesn't keep retrying forever.
Adds an 'Exportieren' button next to 'Alle entfernen' that writes a
pipe-delimited log of every row currently shown in the recent-uploads
panel — so session data doesn't get lost if the log file path is wrong.
Also fixes appendUploadLog silently failing: if the configured path is
unwritable (e.g. C:/Users/<nonexistent>/...), entries now go to
<userData>/fileuploader-fallback.log and the renderer warns once.
Try app-internal key first (new format); on failure, signal the
renderer to prompt for the old password and retry. Lets users import
.mhu files that were exported with a custom password in v2.7.6 or
earlier without downgrading.
File stays AES-GCM encrypted with a fixed app-internal key — opaque
without the app, which is the only protection we actually need for
locally-stored API keys. Removes the modal and both password dialogs.
Previously, clicking 'Ausgewählte starten' on 'Wartet' jobs during an
active upload just showed a toast. But the jobs might NOT actually be
in the batch (skipped during task building).
Now: ALL selected queued/error/aborted jobs are sent to addJobsToBatch.
The upload-manager has duplicate protection (checks jobAbortControllers)
so jobs already in the batch are skipped. Jobs NOT in the batch get
added and start uploading immediately.
Toast now shows exact counts: "X hinzugefügt, Y waren schon im Batch"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the app restarts with a restored queue, it now automatically
reads all fileuploader.log files and removes jobs that were already
successfully uploaded in a previous session.
This prevents re-uploading files that completed before a crash/close.
The dedup runs silently before the UI renders — no user action needed.
Also adds 'read-own-upload-log' IPC that reads all log variants
(base + daily logs) without file picker.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Previously, 'Erneut versuchen' and 'Ausgewählte starten' did nothing
when a batch was already running (uploading=true). Failed jobs were
set to 'Wartet' but never actually uploaded because they couldn't be
added to the running batch.
New: upload-manager.addJobs() allows adding tasks to a running batch.
When a batch is active and user retries/starts jobs, they're injected
into the running batch via IPC 'add-jobs-to-batch'. The upload manager
starts processing them immediately using the existing semaphores.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When buildUploadTasksFromJobs skips jobs (e.g. no valid account for
a hoster), the main process now returns their IDs + reason. The
renderer marks them as 'error' with a descriptive message instead of
leaving them stuck in 'Wartet' (queued) status with no feedback.
Previously: jobs silently stayed at 'Wartet' forever if their hoster
had no configured/enabled account. User had no idea why they weren't
uploading.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ESLint prefer-const auto-fix: 12 variables changed from let to const
where the reference is never reassigned (Maps, Sets, sort state objects).
All tools clean:
- ESLint: 0 errors, 0 warnings
- Tests: 70/70 pass
- npm audit (runtime): 0 vulnerabilities
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
clearHistory() was the only configStore write call not awaited in its
IPC handler. The renderer received 'success' before the file write
completed — closing the app immediately after could leave history intact.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rebuild _jobIndexById after restoring queue from config on startup
(prevented progress updates from finding restored jobs)
- Show and focus mainWindow when files are dropped on floating
drop-target while window is minimized/hidden
- Escape status text in queue table HTML to prevent XSS from
unexpected status values
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Folder monitor: clear _seenFiles entry on file unlink so re-added
files (e.g. re-encoded) are detected again
- Sync IPC save (beforeunload): use atomic write pattern with backup
(.bak) creation, matching the async _atomicWrite behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical: handleShutdownAfterFinish() captured shutdown mode in a
closure at scheduling time — changing mode during countdown was ignored,
causing unexpected system shutdown/restart/sleep.
Now reads shutdownMode at execution time, clears timer when mode
changes to 'nothing', clears orphaned timers before creating new ones,
and adds error handling on exec() calls.
Also: guard stats timer against double-start in upload-manager.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- batch-done handler: appendHistory failure no longer prevents the
upload-batch-done event from reaching the renderer (UI would get stuck)
- remote:input-event: validate x/y as finite numbers before passing
to sendInputEvent (prevents NaN/Infinity crash)
- VOE upload server: wrap JSON.parse in try-catch with clear error
message instead of raw stack trace
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Upload button no longer gets permanently stuck if startUpload()
throws after health check (try-catch with uploading=false reset)
- Wait for running health check instead of silently blocking upload
- Add abort signal check in VOE/Vidmoly upload generators
- Escape filenames with quotes/backslashes in multipart form headers
(all 4 uploaders: doodstream, voe, vidmoly, byse)
- Validate backup import structure before overwriting config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Config write serialization via _writeQueue prevents concurrent
read-modify-write races between settings/queue/history saves
- Cancel active uploads on app quit (prevents zombie processes)
- Persist queue before update install (prevents queue loss)
- Sync IPC save in beforeunload (guarantees save before close)
- Fix double configStore.load() call
- Guard against status regression in handleProgress (done→uploading)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Doodstream requires 2FA, the account modal now dynamically
shows an OTP input field so the user can enter the code from
their email and complete the login without restarting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Windows 10/11 getBounds() includes ~7px invisible resize borders that
are not included in the window capture, causing click offset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove restrictive resolution constraints, capture at native res
- Account for window frame/title bar when mapping click coordinates
(capture includes title bar but sendInputEvent is content-relative)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of enumerating all sources and matching by title (which falls
back to full screen capture), use BrowserWindow.getMediaSourceId() to
get the exact media source ID for the app window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Capture window logs now forwarded to main process via IPC to diagnose
why video tracks are missing from the WebRTC answer SDP.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- desktopCapturer now searches window+screen types with fallbacks
- Partial title match and screen fallback if exact match fails
- Error messages sent back from capture window via IPC
- Detailed logging for capture source selection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The capture window creation is async but the browser's WebRTC offer
arrives immediately after auth. Messages were silently dropped during
window initialization, preventing video stream from establishing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Log files are now created per day (e.g. fileuploader-2026-03-12.log)
instead of per app session. Multiple sessions on the same day append
to the same file. Rolls over automatically at midnight.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Multiple accounts per hoster with drag-sortable priority (primary + fallbacks)
- Separate account types: Web Login and API selectable per hoster
- Account fallback: after all retries fail, automatically switches to next fallback account
- Fix: Byse health check returning [Fehler] OK when API responds with msg "OK"
- Fix: retry during active upload sets status to "Wartet" instead of "Bereit"
- Config migration from single-object to multi-account array format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Small always-on-top drop target window (toggle in Settings > Allgemein)
- Files dropped on it get added to the queue with hoster modal
- Auto-shows on app start if previously enabled
- Column headers now in English (Filename, Uploaded/Size, Progress)
- Statusbar labels in English (Connections, Total)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New FolderMonitor class with chokidar for watching folders
- Settings UI panel with all options (extensions filter, recursive, auto-start, skip duplicates)
- Auto-queue and auto-upload when files appear in monitored folder
- Fix statusbar to show uploaded/remaining instead of cumulative session bytes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "+ Ordner" button for recursive folder upload
- Drag & drop auto-detects folders and resolves files recursively
- Minimize to system tray instead of taskbar
- Tray icon with context menu (Öffnen/Beenden)
- Tray tooltip shows upload progress during active uploads
- Fix folder detection heuristic (size === 0, not % 4096)
- Fix concurrent drop guard to prevent double modal
- Fix duplicate "Erneut versuchen" context menu entry
- Add .catch() on async drop handlers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>