Compare commits

..

No commits in common. "2efbc355b312c8d9ccbf449e98d98d00cbc0879b" and "e216c95b6159be63478ad10c922e7cab0010bf4e" have entirely different histories.

4 changed files with 8 additions and 57 deletions

View File

@ -17,10 +17,7 @@
const parts = trimmed.split('|'); const parts = trimmed.split('|');
if (parts.length < 5) return null; if (parts.length < 5) return null;
const hoster = (parts[1] || '').trim(); const hoster = (parts[1] || '').trim();
let fileName = ''; const fileName = (parts[4] || '').trim();
for (let i = parts.length - 1; i >= 4; i--) {
if (parts[i].trim() !== '') { fileName = parts[i]; break; }
}
if (!hoster || !fileName) return null; if (!hoster || !fileName) return null;
const tsStr = (parts[0] || '').trim(); const tsStr = (parts[0] || '').trim();
const tsParsed = tsStr ? Date.parse(tsStr.replace(' ', 'T')) : NaN; const tsParsed = tsStr ? Date.parse(tsStr.replace(' ', 'T')) : NaN;

View File

@ -1,6 +1,6 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "3.3.82", "version": "3.3.81",
"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

@ -671,12 +671,6 @@ function applyHosterSelection() {
selectedFiles.push(..._pendingFiles); selectedFiles.push(..._pendingFiles);
_pendingFiles = []; _pendingFiles = [];
} }
if (pendingPaths.size > 0) {
for (const key of [..._completedUploadKeys]) {
const sep = key.lastIndexOf('|');
if (sep > 0 && pendingPaths.has(key.slice(0, sep))) _completedUploadKeys.delete(key);
}
}
renderHosterSummary(); renderHosterSummary();
// During an active upload, build preview jobs for the new files and inject // During an active upload, build preview jobs for the new files and inject
@ -719,12 +713,6 @@ function restoreQueueStateFromConfig() {
? pending.savedAt ? pending.savedAt
: null; : null;
if (Array.isArray(pending.completedKeys)) {
for (const k of pending.completedKeys) {
if (typeof k === 'string' && k) _completedUploadKeys.add(k);
}
}
selectedUploadHosters = Array.isArray(pending.selectedUploadHosters) selectedUploadHosters = Array.isArray(pending.selectedUploadHosters)
? pending.selectedUploadHosters.filter(Boolean) ? pending.selectedUploadHosters.filter(Boolean)
: selectedUploadHosters; : selectedUploadHosters;
@ -797,16 +785,10 @@ function buildPersistedQueueState() {
// consistent "Bereit" for everything that didn't actually terminate. // consistent "Bereit" for everything that didn't actually terminate.
// Only true terminal states (done / error / skipped) survive as-is. // Only true terminal states (done / error / skipped) survive as-is.
const TERMINAL = new Set(['done', 'error', 'skipped']); const TERMINAL = new Set(['done', 'error', 'skipped']);
const completedKeys = [];
for (const k of _completedUploadKeys) {
const sep = k.lastIndexOf('|');
if (sep > 0 && selectedFileMap.has(k.slice(0, sep))) completedKeys.push(k);
}
return { return {
savedAt: Date.now(), savedAt: Date.now(),
selectedUploadHosters: getSelectedHosters(), selectedUploadHosters: getSelectedHosters(),
selectedFiles: Array.from(selectedFileMap.values()), selectedFiles: Array.from(selectedFileMap.values()),
completedKeys,
queueJobs: queueJobs.map(job => { queueJobs: queueJobs.map(job => {
const isTerminal = TERMINAL.has(job.status); const isTerminal = TERMINAL.has(job.status);
return { return {
@ -1078,7 +1060,7 @@ function buildQueuePreview() {
// Build a Set for fast existence checks // Build a Set for fast existence checks
const existingKeys = new Set(); const existingKeys = new Set();
for (const j of queueJobs) { for (const j of queueJobs) {
existingKeys.add(`${j.file}|${j.hoster}`); if (j.status !== 'error') existingKeys.add(`${j.file}|${j.hoster}`);
} }
for (const file of selectedFiles) { for (const file of selectedFiles) {
@ -1117,14 +1099,14 @@ function indexJob(job) {
if (job.uploadId) _jobIndexByUploadId.set(job.uploadId, job); if (job.uploadId) _jobIndexByUploadId.set(job.uploadId, job);
} }
function removeJobFromIndex(job, keepCompletedKey) { function removeJobFromIndex(job) {
_jobIndexById.delete(job.id); _jobIndexById.delete(job.id);
if (job.uploadId) _jobIndexByUploadId.delete(job.uploadId); if (job.uploadId) _jobIndexByUploadId.delete(job.uploadId);
// Track deletion so handleProgress() won't re-create this job from stale callbacks // Track deletion so handleProgress() won't re-create this job from stale callbacks
_deletedJobIds.add(job.id); _deletedJobIds.add(job.id);
if (job.uploadId) _deletedJobIds.add(job.uploadId); if (job.uploadId) _deletedJobIds.add(job.uploadId);
// Allow re-uploading same file+hoster after deletion // Allow re-uploading same file+hoster after deletion
if (!keepCompletedKey && job.file && job.hoster) _completedUploadKeys.delete(`${job.file}|${job.hoster}`); if (job.file && job.hoster) _completedUploadKeys.delete(`${job.file}|${job.hoster}`);
} }
// --- Queue Table Rendering (debounced with virtual scrolling) --- // --- Queue Table Rendering (debounced with virtual scrolling) ---
@ -2267,7 +2249,7 @@ function _handleProgressImpl(data) {
// updated synchronously so subsequent lookups see the right state — only // updated synchronously so subsequent lookups see the right state — only
// the array rewrite is deferred. // the array rewrite is deferred.
if (job.status === 'done' && config.globalSettings && config.globalSettings.removeFromQueueOnDone) { if (job.status === 'done' && config.globalSettings && config.globalSettings.removeFromQueueOnDone) {
removeJobFromIndex(job, true); removeJobFromIndex(job);
selectedJobIds.delete(job.id); selectedJobIds.delete(job.id);
if (_doneRemovalCoalescer) { if (_doneRemovalCoalescer) {
_doneRemovalCoalescer.add(job.id); _doneRemovalCoalescer.add(job.id);
@ -2333,7 +2315,7 @@ function handleBatchDone(summary) {
const nextJobs = []; const nextJobs = [];
for (const job of queueJobs) { for (const job of queueJobs) {
if (job.status === 'done') { if (job.status === 'done') {
removeJobFromIndex(job, true); removeJobFromIndex(job);
selectedJobIds.delete(job.id); selectedJobIds.delete(job.id);
} else { } else {
nextJobs.push(job); nextJobs.push(job);
@ -2355,7 +2337,7 @@ function handleBatchDone(summary) {
const result = window.QueuePrune?.pruneOldestTerminalJobs(queueJobs, TERMINAL_KEEP_LIMIT); const result = window.QueuePrune?.pruneOldestTerminalJobs(queueJobs, TERMINAL_KEEP_LIMIT);
if (result) { if (result) {
for (const j of result.dropped) { for (const j of result.dropped) {
removeJobFromIndex(j, true); removeJobFromIndex(j);
selectedJobIds.delete(j.id); selectedJobIds.delete(j.id);
} }
queueJobs = result.kept; queueJobs = result.kept;

View File

@ -50,31 +50,3 @@ test('parseUploadLogLine: missing/garbage timestamp yields ts=undefined (legacy
assert.equal(parsed.fileName, 'a.mkv'); assert.equal(parsed.fileName, 'a.mkv');
assert.equal(parsed.ts, undefined); assert.equal(parsed.ts, undefined);
}); });
test('parseUploadLogLine: a pipe in the link does NOT shift the filename field (entry not lost)', () => {
const line = formatUploadLogLine(new Date(2026, 5, 19, 12, 0, 0), 'byse.sx', 'https://h.io/a|b', 'movie.mkv');
const parsed = parseUploadLogLine(line);
assert.equal(parsed.hoster, 'byse.sx');
assert.equal(parsed.fileName, 'movie.mkv', 'filename is taken as the last non-empty field, robust to link pipes');
});
test('parseUploadLogLine: two pipes in the link still parse the correct filename', () => {
const line = formatUploadLogLine(new Date(2026, 5, 19, 12, 0, 0), 'byse.sx', 'https://h.io/a|b|c', 'movie.mkv');
const parsed = parseUploadLogLine(line);
assert.equal(parsed.fileName, 'movie.mkv');
});
test('parseUploadLogLine: a leading-space filename is preserved (matches the untrimmed queue-job key)', () => {
const line = formatUploadLogLine(new Date(2026, 5, 19, 12, 0, 0), 'voe.sx', 'https://h.io/a', ' movie.mkv');
const parsed = parseUploadLogLine(line);
assert.equal(parsed.fileName, ' movie.mkv', 'filename is NOT trimmed, so it matches the OS basename verbatim');
});
test('SEAM: a leading-space filename round-trips and the gate still drops its ghost', () => {
const completion = new Date(2026, 5, 19, 12, 0, 30);
const parsed = parseUploadLogLine(formatUploadLogLine(completion, 'voe.sx', 'l', ' spaced.mp4'));
const savedAt = completion.getTime() - 5000;
const job = { status: 'preview', fileName: ' spaced.mp4', hoster: 'voe.sx', file: 'C:/dl/ spaced.mp4' };
const { removed } = partitionRestoredJobsByLog([job], [parsed], savedAt);
assert.equal(removed.length, 1, 'leading-space filename now matches end-to-end (was a mismatch before)');
});