Compare commits

..

No commits in common. "b4c26f81060fe944979d555b7dbb811b2e8863d3" and "c73108afff914b78cbc5f42429a33ac16520a13e" have entirely different histories.

5 changed files with 16 additions and 50 deletions

View File

@ -155,13 +155,9 @@ class ClouddropUploader {
const totalChunks = initPayload.totalChunks || Math.ceil(fileSize / chunkSize); const totalChunks = initPayload.totalChunks || Math.ceil(fileSize / chunkSize);
if (!sessionId) throw new Error('Clouddrop: Keine sessionId von /upload/init'); if (!sessionId) throw new Error('Clouddrop: Keine sessionId von /upload/init');
// 2. Read file and PUT chunks sequentially. // 2. Read file and PUT chunks sequentially
// Reuse a single buffer for all chunks (only the last chunk may be smaller,
// in which case we slice a view). Avoids 64× 16 MB allocations on a 1 GB
// file — real GC pressure during busy uploads.
const fd = fs.openSync(filePath, 'r'); const fd = fs.openSync(filePath, 'r');
let bytesSent = 0; let bytesSent = 0;
const reusableBuf = Buffer.allocUnsafe(chunkSize);
try { try {
for (let i = 0; i < totalChunks; i++) { for (let i = 0; i < totalChunks; i++) {
if (signal && signal.aborted) throw new Error('Aborted'); if (signal && signal.aborted) throw new Error('Aborted');
@ -169,10 +165,8 @@ class ClouddropUploader {
const offset = i * chunkSize; const offset = i * chunkSize;
const remaining = fileSize - offset; const remaining = fileSize - offset;
const thisChunkSize = Math.min(chunkSize, remaining); const thisChunkSize = Math.min(chunkSize, remaining);
fs.readSync(fd, reusableBuf, 0, thisChunkSize, offset); const buf = Buffer.alloc(thisChunkSize);
const body = thisChunkSize === chunkSize fs.readSync(fd, buf, 0, thisChunkSize, offset);
? reusableBuf
: reusableBuf.subarray(0, thisChunkSize);
if (throttle) await throttle.consume(thisChunkSize, signal); if (throttle) await throttle.consume(thisChunkSize, signal);
@ -180,7 +174,7 @@ class ClouddropUploader {
method: 'PUT', method: 'PUT',
dispatcher: clouddropAgent, dispatcher: clouddropAgent,
signal, signal,
body, body: buf,
headers: this._headers({ headers: this._headers({
'Content-Type': 'application/octet-stream', 'Content-Type': 'application/octet-stream',
'Content-Length': String(thisChunkSize) 'Content-Length': String(thisChunkSize)

View File

@ -361,11 +361,7 @@ class UploadManager extends EventEmitter {
}, 2000); }, 2000);
} }
// Mutate this single object on each progress callback instead of this.activeJobs.set(uploadId, { jobId, speedKbs: 0, bytesUploaded: 0 });
// allocating a fresh one — callback fires on every stream chunk
// (hundreds/sec per active job).
const activeEntry = { jobId, speedKbs: 0, bytesUploaded: 0 };
this.activeJobs.set(uploadId, activeEntry);
let lastEmitTime = 0; let lastEmitTime = 0;
const PROGRESS_EMIT_INTERVAL = 250; // ms throttle UI updates const PROGRESS_EMIT_INTERVAL = 250; // ms throttle UI updates
@ -381,8 +377,7 @@ class UploadManager extends EventEmitter {
lastSpeedTime = now; lastSpeedTime = now;
} }
activeEntry.speedKbs = currentSpeedKbs; this.activeJobs.set(uploadId, { jobId, speedKbs: currentSpeedKbs, bytesUploaded });
activeEntry.bytesUploaded = bytesUploaded;
// Throttle progress emissions to reduce IPC + rendering overhead // Throttle progress emissions to reduce IPC + rendering overhead
if (now - lastEmitTime < PROGRESS_EMIT_INTERVAL) return; if (now - lastEmitTime < PROGRESS_EMIT_INTERVAL) return;
@ -587,17 +582,18 @@ class UploadManager extends EventEmitter {
_startStatsTimer() { _startStatsTimer() {
if (this.statsInterval) clearInterval(this.statsInterval); if (this.statsInterval) clearInterval(this.statsInterval);
this.statsInterval = setInterval(() => { this.statsInterval = setInterval(() => {
// Single pass over active jobs instead of two.
let globalSpeedKbs = 0; let globalSpeedKbs = 0;
let activeCount = 0; let activeCount = 0;
let inProgressBytes = 0;
for (const job of this.activeJobs.values()) { for (const job of this.activeJobs.values()) {
globalSpeedKbs += job.speedKbs || 0; globalSpeedKbs += job.speedKbs || 0;
inProgressBytes += job.bytesUploaded || 0;
activeCount++; activeCount++;
} }
const elapsed = Math.round((Date.now() - this.startTime) / 1000); const elapsed = Math.round((Date.now() - this.startTime) / 1000);
let inProgressBytes = 0;
for (const job of this.activeJobs.values()) {
inProgressBytes += job.bytesUploaded || 0;
}
this.emit('stats', { this.emit('stats', {
state: this.running ? (this.stopAfterActive ? 'stopping' : 'uploading') : 'idle', state: this.running ? (this.stopAfterActive ? 'stopping' : 'uploading') : 'idle',

29
main.js
View File

@ -165,33 +165,12 @@ const _uploadLogBuffer = [];
let _uploadLogFlushTimer = null; let _uploadLogFlushTimer = null;
let _uploadLogWriting = false; let _uploadLogWriting = false;
// Cache the resolved upload-log target across flushes — mkdirSync + path
// assembly on every 500ms flush during uploads is wasted work once we've
// confirmed a writable directory. Invalidated when the user changes the log
// path or when the daily-log date rolls over.
let _cachedUploadLogTarget = null;
let _cachedUploadLogKey = '';
function _invalidateUploadLogTargetCache() {
_cachedUploadLogTarget = null;
_cachedUploadLogKey = '';
}
function _resolveUploadLogTarget() { function _resolveUploadLogTarget() {
const primary = getLogFilePath();
const key = `${primary}|${_dailyLogDate || ''}`;
if (_cachedUploadLogKey === key && _cachedUploadLogTarget) return _cachedUploadLogTarget;
const commit = (t) => {
_cachedUploadLogTarget = t;
_cachedUploadLogKey = key;
return t;
};
// Try primary → desktop → userData, mirror the original fallback ladder. // Try primary → desktop → userData, mirror the original fallback ladder.
const primary = getLogFilePath();
try { try {
fs.mkdirSync(path.dirname(primary), { recursive: true }); fs.mkdirSync(path.dirname(primary), { recursive: true });
return commit({ path: primary, isFallback: false }); return { path: primary, isFallback: false };
} catch (err) { } catch (err) {
debugLog(`uploadLog primary dir unavailable (${err.message})`); debugLog(`uploadLog primary dir unavailable (${err.message})`);
} }
@ -200,13 +179,13 @@ function _resolveUploadLogTarget() {
try { try {
const p = buildFallbackLogName(desktop); const p = buildFallbackLogName(desktop);
fs.mkdirSync(path.dirname(p), { recursive: true }); fs.mkdirSync(path.dirname(p), { recursive: true });
return commit({ path: p, isFallback: true }); return { path: p, isFallback: true };
} catch {} } catch {}
} }
try { try {
const p = buildFallbackLogName(app.getPath('userData')); const p = buildFallbackLogName(app.getPath('userData'));
fs.mkdirSync(path.dirname(p), { recursive: true }); fs.mkdirSync(path.dirname(p), { recursive: true });
return commit({ path: p, isFallback: true }); return { path: p, isFallback: true };
} catch (err) { } catch (err) {
debugLog(`uploadLog: no writable target (${err.message})`); debugLog(`uploadLog: no writable target (${err.message})`);
return null; return null;

View File

@ -1,6 +1,6 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "2.8.9", "version": "2.8.8",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously", "description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@ -3264,7 +3264,6 @@ function renderRecentUploadsPanel() {
&& rows.length > _recentLastRenderedLen && rows.length > _recentLastRenderedLen
&& tbody.querySelectorAll('.recent-file-row').length === _recentLastRenderedLen; && tbody.querySelectorAll('.recent-file-row').length === _recentLastRenderedLen;
let wasAppendOnly = false;
if (dateDescAppendOnly) { if (dateDescAppendOnly) {
// Fast path: only new rows (date desc puts newest on top) — insert them // Fast path: only new rows (date desc puts newest on top) — insert them
// at the top without rebuilding the 5000-row tbody below. // at the top without rebuilding the 5000-row tbody below.
@ -3272,7 +3271,6 @@ function renderRecentUploadsPanel() {
let html = ''; let html = '';
for (let i = 0; i < added; i++) html += _buildRecentRowHtml(rows[i]); for (let i = 0; i < added; i++) html += _buildRecentRowHtml(rows[i]);
tbody.insertAdjacentHTML('afterbegin', html); tbody.insertAdjacentHTML('afterbegin', html);
wasAppendOnly = true;
} else { } else {
tbody.innerHTML = rows.map(_buildRecentRowHtml).join(''); tbody.innerHTML = rows.map(_buildRecentRowHtml).join('');
} }
@ -3318,8 +3316,7 @@ function renderRecentUploadsPanel() {
}); });
} }
// Sort headers only change when the sort state changes — skip on appends. updateRecentSortHeaders();
if (!wasAppendOnly) updateRecentSortHeaders();
} }
function renderHistoryTable(container) { function renderHistoryTable(container) {