|
|
|
@ -99,6 +99,7 @@ async function init() {
|
|
|
|
syncSelectedUploadHosters();
|
|
|
|
syncSelectedUploadHosters();
|
|
|
|
restoreQueueStateFromConfig();
|
|
|
|
restoreQueueStateFromConfig();
|
|
|
|
await _autoDeduplicateFromLog();
|
|
|
|
await _autoDeduplicateFromLog();
|
|
|
|
|
|
|
|
_hydrateMissingJobSizes();
|
|
|
|
renderHosterSummary();
|
|
|
|
renderHosterSummary();
|
|
|
|
renderHosterModal();
|
|
|
|
renderHosterModal();
|
|
|
|
renderSettings();
|
|
|
|
renderSettings();
|
|
|
|
@ -997,12 +998,54 @@ function scheduleStatusChangeUpdate() {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function _hydrateMissingJobSizes(jobsLike) {
|
|
|
|
|
|
|
|
if (!window.api || !window.api.getFileSizes) return;
|
|
|
|
|
|
|
|
const paths = [];
|
|
|
|
|
|
|
|
const seen = new Set();
|
|
|
|
|
|
|
|
const source = Array.isArray(jobsLike) ? jobsLike : queueJobs;
|
|
|
|
|
|
|
|
for (const j of source) {
|
|
|
|
|
|
|
|
if (!j || !j.file) continue;
|
|
|
|
|
|
|
|
if (j.bytesTotal && j.bytesTotal > 0) continue;
|
|
|
|
|
|
|
|
if (seen.has(j.file)) continue;
|
|
|
|
|
|
|
|
seen.add(j.file);
|
|
|
|
|
|
|
|
paths.push(j.file);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (paths.length === 0) return;
|
|
|
|
|
|
|
|
window.api.getFileSizes(paths).then((sizeMap) => {
|
|
|
|
|
|
|
|
if (!sizeMap || typeof sizeMap !== 'object') return;
|
|
|
|
|
|
|
|
let changed = false;
|
|
|
|
|
|
|
|
for (const j of queueJobs) {
|
|
|
|
|
|
|
|
if (sizeMap[j.file] && (!j.bytesTotal || j.bytesTotal === 0)) {
|
|
|
|
|
|
|
|
j.bytesTotal = sizeMap[j.file];
|
|
|
|
|
|
|
|
changed = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const f of selectedFiles) {
|
|
|
|
|
|
|
|
if (sizeMap[f.path] && (!f.size || f.size === 0)) f.size = sizeMap[f.path];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
|
|
|
_queueStatsCache = null;
|
|
|
|
|
|
|
|
if (typeof renderQueueTable === 'function') renderQueueTable();
|
|
|
|
|
|
|
|
if (typeof updateStatusBar === 'function') updateStatusBar();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}).catch(() => {});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function _formatUploadedSize(job) {
|
|
|
|
|
|
|
|
const bt = job.bytesTotal || 0;
|
|
|
|
|
|
|
|
const bu = job.bytesUploaded || 0;
|
|
|
|
|
|
|
|
const s = job.status;
|
|
|
|
|
|
|
|
if (s === 'preview') return bt > 0 ? formatSize(bt) : '...';
|
|
|
|
|
|
|
|
if (s === 'queued' || s === 'getting-server' || s === 'retrying') {
|
|
|
|
|
|
|
|
return bt > 0 ? `${formatSize(bu)} / ${formatSize(bt)}` : '...';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return `${formatSize(bu)} / ${formatSize(bt)}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildRowHtml(job) {
|
|
|
|
function buildRowHtml(job) {
|
|
|
|
const statusClass = `status-${job.status}`;
|
|
|
|
const statusClass = `status-${job.status}`;
|
|
|
|
const rowClass = `queue-row ${statusClass}${selectedJobIds.has(job.id) ? ' selected' : ''}`;
|
|
|
|
const rowClass = `queue-row ${statusClass}${selectedJobIds.has(job.id) ? ' selected' : ''}`;
|
|
|
|
const uploadedSize = job.status === 'preview'
|
|
|
|
const uploadedSize = _formatUploadedSize(job);
|
|
|
|
? (job.bytesTotal > 0 ? formatSize(job.bytesTotal) : '...')
|
|
|
|
|
|
|
|
: `${formatSize(job.bytesUploaded)} / ${formatSize(job.bytesTotal)}`;
|
|
|
|
|
|
|
|
const statusText = getStatusText(job);
|
|
|
|
const statusText = getStatusText(job);
|
|
|
|
const elapsed = formatTime(job.elapsed);
|
|
|
|
const elapsed = formatTime(job.elapsed);
|
|
|
|
const remaining = formatTime(job.remaining);
|
|
|
|
const remaining = formatTime(job.remaining);
|
|
|
|
@ -1032,9 +1075,7 @@ function buildRowHtml(job) {
|
|
|
|
// In-place update of a single row's cells (avoids full innerHTML rebuild)
|
|
|
|
// In-place update of a single row's cells (avoids full innerHTML rebuild)
|
|
|
|
function _updateRowInPlace(tr, job) {
|
|
|
|
function _updateRowInPlace(tr, job) {
|
|
|
|
const statusClass = `status-${job.status}`;
|
|
|
|
const statusClass = `status-${job.status}`;
|
|
|
|
const uploadedSize = job.status === 'preview'
|
|
|
|
const uploadedSize = _formatUploadedSize(job);
|
|
|
|
? (job.bytesTotal > 0 ? formatSize(job.bytesTotal) : '...')
|
|
|
|
|
|
|
|
: `${formatSize(job.bytesUploaded)} / ${formatSize(job.bytesTotal)}`;
|
|
|
|
|
|
|
|
const statusText = getStatusText(job);
|
|
|
|
const statusText = getStatusText(job);
|
|
|
|
const elapsed = formatTime(job.elapsed);
|
|
|
|
const elapsed = formatTime(job.elapsed);
|
|
|
|
const remaining = formatTime(job.remaining);
|
|
|
|
const remaining = formatTime(job.remaining);
|
|
|
|
@ -1728,6 +1769,7 @@ async function startUpload() {
|
|
|
|
if (uploading) return;
|
|
|
|
if (uploading) return;
|
|
|
|
uploading = true; // set immediately to prevent double-click race
|
|
|
|
uploading = true; // set immediately to prevent double-click race
|
|
|
|
updateQueueActionButtons();
|
|
|
|
updateQueueActionButtons();
|
|
|
|
|
|
|
|
_hydrateMissingJobSizes();
|
|
|
|
|
|
|
|
|
|
|
|
const hosters = getSelectedHosters();
|
|
|
|
const hosters = getSelectedHosters();
|
|
|
|
if (queueJobs.length === 0 && selectedFiles.length > 0) {
|
|
|
|
if (queueJobs.length === 0 && selectedFiles.length > 0) {
|
|
|
|
@ -1800,10 +1842,13 @@ function _markSkippedJobs(result) {
|
|
|
|
|
|
|
|
|
|
|
|
async function startSelectedUpload() {
|
|
|
|
async function startSelectedUpload() {
|
|
|
|
if (uploading) {
|
|
|
|
if (uploading) {
|
|
|
|
// Batch already running — add selected jobs (queued/error/aborted/skipped) to running batch
|
|
|
|
_hydrateMissingJobSizes();
|
|
|
|
// Upload-manager has duplicate protection (skips jobs already tracked)
|
|
|
|
const addable = queueJobs.filter(j => selectedJobIds.has(j.id) && isStartableQueueStatus(j.status));
|
|
|
|
const addable = queueJobs.filter(j => selectedJobIds.has(j.id) && ['queued', 'error', 'aborted', 'skipped'].includes(j.status));
|
|
|
|
if (addable.length === 0) {
|
|
|
|
if (addable.length > 0) {
|
|
|
|
if (selectedJobIds.size > 0) showCopyToast('Keine startbaren Jobs ausgewählt (alle laufen schon oder sind fertig).');
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
addable.forEach(j => {
|
|
|
|
addable.forEach(j => {
|
|
|
|
j.status = 'queued'; j.error = null; j.result = null;
|
|
|
|
j.status = 'queued'; j.error = null; j.result = null;
|
|
|
|
j.bytesUploaded = 0; j.speedKbs = 0; j.progress = 0; j.uploadId = null;
|
|
|
|
j.bytesUploaded = 0; j.speedKbs = 0; j.progress = 0; j.uploadId = null;
|
|
|
|
@ -1848,7 +1893,6 @@ async function startSelectedUpload() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uploading = true; // set immediately to prevent double-click race
|
|
|
|
uploading = true; // set immediately to prevent double-click race
|
|
|
|
updateQueueActionButtons();
|
|
|
|
updateQueueActionButtons();
|
|
|
|
@ -2108,7 +2152,6 @@ function handleBatchDone(summary) {
|
|
|
|
|
|
|
|
|
|
|
|
lastUploadStats = { state: 'idle', globalSpeedKbs: 0, totalBytes: lastUploadStats.totalBytes, elapsed: lastUploadStats.elapsed, activeJobs: 0 };
|
|
|
|
lastUploadStats = { state: 'idle', globalSpeedKbs: 0, totalBytes: lastUploadStats.totalBytes, elapsed: lastUploadStats.elapsed, activeJobs: 0 };
|
|
|
|
updateStatusBar();
|
|
|
|
updateStatusBar();
|
|
|
|
_maybeShowBatchSummary(summary);
|
|
|
|
|
|
|
|
_refreshSessionFailedSnapshot();
|
|
|
|
_refreshSessionFailedSnapshot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|