Three state bugs found during audit:
1. _failedAccounts / _accountOverrides survived across batches. A
rate-limited account from batch 1 stayed permanently blacklisted
for the rest of the app session, so batch 2 skipped straight to
the fallback even after the original recovered. Now cleared in
startBatch so each run evaluates accounts fresh.
2. Account rotation was one level deep. With three accounts [A,B,C]
on the same hoster and A + B both failing, the job errored out
— C was never tried. The fallback-retry was a single if-block.
Replaced with a while-loop that keeps asking main for the next
override and rotating until every account is exhausted.
3. Queue sort cache included 'size' as a static key, but bytesTotal
goes 0 → actual when previews resolve. A queue sorted by size
during preview would cache the all-zeros order and never update.
Removed size from _STATIC_SORT_KEYS — it now re-sorts per render
like status/speed/progress.
Hot path on large table rebuilds — every text cell runs through one
of these. Switching from 4 chained .replace() calls to a single regex
with a lookup map is ~3× faster. At 5000 rows × 4 fields per rebuild,
80k → 20k regex operations.
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.
Three more targeted wins:
- loadHistory() was called unconditionally on every handleBatchDone,
doing an IPC roundtrip + full history-table rebuild even when the
user is on the Upload tab and can't see it. Now it sets a dirty
flag and the actual refresh is deferred until the user switches
to the Verlauf tab. On a fresh tab click it always runs.
- renderRecentUploadsPanel append-only fast path: when the sort is
'date desc' (the default) and the dataset only grew, the panel
inserts the new rows at the top via insertAdjacentHTML instead
of rebuilding the 5000-row tbody from scratch. Length shrinks or
sort-change still trigger a full rebuild.
- handleBatchDone's removeFromQueueOnDone cleanup now does one pass
(build keep-list + detach from index together) instead of two
separate filter() scans over queueJobs.
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.
Three more wins on top of the previous pass:
- sortQueueJobs memoizes the result for static sort keys
(filename, host, size) — these don't change during upload, so
every 200ms progress render now reuses the same sorted array
instead of running an O(n log n) Collator compare.
- _computeQueueStats caches within a single tick via queueMicrotask.
updateStatusBar + updateStatsPanel are always called back-to-back
and now share one queue scan instead of running two.
- _updateRowInPlace writes DOM values only when they actually
changed. Idle/queued/done rows (the majority) incur zero DOM
mutations per progress tick.
The two worst hot paths were:
- clicking a row triggered a full table rebuild with sort+innerHTML
(queue AND recent panel), and the opposite panel got cleared with
another full rebuild
- every upload progress tick (4/sec) scanned queueJobs twice and
filtered sessionFilesData twice just to update the status bar
Fixes:
- applyQueueSelectionClasses / applyRecentSelectionClasses toggle the
.selected class on existing rows instead of rebuilding the tbody.
Click selection is now O(rendered rows) instead of O(total × sort).
- maybeAddSessionFile schedules renderRecentUploadsPanel via rAF so
a batch of 1000 successful uploads coalesces into one render.
- sortRecentFiles memoizes its result per (sortKey, direction, len)
— unchanged sort state + unchanged length returns the cached array
instead of re-sorting thousands of entries.
- _computeQueueStats now also returns inProgressBytes, dropping the
second queueJobs scan in updateStatusBar.
- session done/error counts are maintained incrementally, replacing
two sessionFilesData.filter().length calls every status-bar tick.
- handleRowClick uses the _jobIndexById map instead of Array.find.
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.
/upload/complete was failing (non-JSON response, missing fileId, or
post-processing timeout) after all bytes were already on the server,
causing upload-manager to retry the entire multi-GB upload — which
corrupts the server-side file since two uploads end up interleaved.
Now /complete failures are swallowed and sessionId is used as the
file_code fallback. Upload is considered done once all chunks are in.
Upload completes on server but file is still being processed, so
share-link fails. Retry up to 6x with backoff; on final failure, use
fileId-based fallback URL instead of throwing — prevents upload-manager
from retrying the entire multi-GB upload.
Adds a red 'Alle entfernen' button next to the 'Zuletzt erzeugte
Upload-Links' label that clears all entries from the recent files
panel after confirmation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drag the right edge of any queue column header to resize it. Cursor
changes to col-resize on hover. Widths are saved to localStorage and
restored on next launch.
- Resizer handles in all 8 queue table columns
- Resize state visible via dragging class + body cursor override
- Min width 40px, no max (table can scroll horizontally)
- Click on resizer doesn't trigger column sort
- Persisted across sessions via localStorage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- addJobs injects new tasks into running batch (verified concurrent execution)
- addJobs rejects duplicate jobIds already in batch
- addJobs returns added=0 when not running
These tests verify the fix in v2.6.3 (files added during upload now
get injected into the running batch via addJobsToBatch).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user added new files during an active upload (drag-drop, picker
or folder monitor with pre-selected hosters), the files were pushed to
selectedFiles but NO queue jobs were created (because updateUploadView
skips buildQueuePreview during uploading=true).
The files briefly showed up via folder monitor's direct buildQueuePreview
call, but then handleBatchDone → syncSelectedFilesFromQueue removed them
from selectedFiles because they had no queue jobs.
Now: applyHosterSelection() and folder monitor both detect added files
during upload and:
1. Build preview jobs for the new files
2. Reset them to 'queued' status
3. Inject them into the running batch via addJobsToBatch IPC
The upload-manager has duplicate protection so re-injection is safe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>