Fix extraction status cross-contamination with filename pattern matching, release v1.4.70
Previous fix used pathKey-based maps which failed due to path resolution mismatches on Windows. New approach matches items to archives using filename regex patterns directly (e.g. prefix.part\d+.rar), which is robust regardless of path casing/resolution. Also marks items as "Entpackt" immediately when their archive finishes instead of waiting for all archives to complete, so completed episodes show correct status while later episodes are still extracting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4371e53b86
commit
674cf101da
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.4.69",
|
"version": "1.4.70",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -4415,7 +4415,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
// Build set of item targetPaths belonging to ready archives
|
// Build set of item targetPaths belonging to ready archives
|
||||||
const hybridItemPaths = new Set<string>();
|
const hybridItemPaths = new Set<string>();
|
||||||
const archiveToItems = new Map<string, DownloadItem[]>();
|
|
||||||
let dirFiles: string[] | undefined;
|
let dirFiles: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
dirFiles = fs.readdirSync(pkg.outputDir, { withFileTypes: true })
|
dirFiles = fs.readdirSync(pkg.outputDir, { withFileTypes: true })
|
||||||
@ -4424,22 +4423,35 @@ export class DownloadManager extends EventEmitter {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
for (const archiveKey of readyArchives) {
|
for (const archiveKey of readyArchives) {
|
||||||
const parts = collectArchiveCleanupTargets(archiveKey, dirFiles);
|
const parts = collectArchiveCleanupTargets(archiveKey, dirFiles);
|
||||||
const partKeys = new Set<string>();
|
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
hybridItemPaths.add(pathKey(part));
|
hybridItemPaths.add(pathKey(part));
|
||||||
partKeys.add(pathKey(part));
|
|
||||||
}
|
}
|
||||||
hybridItemPaths.add(pathKey(archiveKey));
|
hybridItemPaths.add(pathKey(archiveKey));
|
||||||
partKeys.add(pathKey(archiveKey));
|
|
||||||
const matched = completedItems.filter((item) => item.targetPath && partKeys.has(pathKey(item.targetPath)));
|
|
||||||
if (matched.length > 0) {
|
|
||||||
archiveToItems.set(path.basename(archiveKey).toLowerCase(), matched);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const hybridItems = completedItems.filter((item) =>
|
const hybridItems = completedItems.filter((item) =>
|
||||||
item.targetPath && hybridItemPaths.has(pathKey(item.targetPath))
|
item.targetPath && hybridItemPaths.has(pathKey(item.targetPath))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Resolve items belonging to a specific archive entry point by filename pattern matching.
|
||||||
|
// This avoids pathKey mismatches by comparing basenames directly.
|
||||||
|
const resolveArchiveItems = (archiveName: string): DownloadItem[] => {
|
||||||
|
const entryLower = archiveName.toLowerCase();
|
||||||
|
const multipartMatch = entryLower.match(/^(.*)\.part0*1\.rar$/);
|
||||||
|
if (multipartMatch) {
|
||||||
|
const prefix = multipartMatch[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const pattern = new RegExp(`^${prefix}\\.part\\d+\\.rar$`, "i");
|
||||||
|
return hybridItems.filter((item) => {
|
||||||
|
const name = path.basename(item.targetPath || item.fileName || "");
|
||||||
|
return pattern.test(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Single-file archive: match only that exact file
|
||||||
|
return hybridItems.filter((item) => {
|
||||||
|
const name = path.basename(item.targetPath || item.fileName || "").toLowerCase();
|
||||||
|
return name === entryLower;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
let currentArchiveItems: DownloadItem[] = hybridItems;
|
let currentArchiveItems: DownloadItem[] = hybridItems;
|
||||||
const updateExtractingStatus = (text: string): void => {
|
const updateExtractingStatus = (text: string): void => {
|
||||||
const normalized = String(text || "");
|
const normalized = String(text || "");
|
||||||
@ -4462,6 +4474,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
let hybridLastStatusText = "";
|
let hybridLastStatusText = "";
|
||||||
let hybridLastEmitAt = 0;
|
let hybridLastEmitAt = 0;
|
||||||
|
let lastHybridArchiveName = "";
|
||||||
const emitHybridStatus = (text: string, force = false): void => {
|
const emitHybridStatus = (text: string, force = false): void => {
|
||||||
updateExtractingStatus(text);
|
updateExtractingStatus(text);
|
||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
@ -4491,9 +4504,20 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (progress.phase === "done") {
|
if (progress.phase === "done") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Narrow status updates to only items belonging to the current archive
|
// When a new archive starts, mark the previous archive's items as "Entpackt"
|
||||||
if (progress.archiveName) {
|
if (progress.archiveName && progress.archiveName !== lastHybridArchiveName) {
|
||||||
currentArchiveItems = archiveToItems.get(progress.archiveName.toLowerCase()) || hybridItems;
|
if (lastHybridArchiveName && currentArchiveItems !== hybridItems) {
|
||||||
|
const doneAt = nowMs();
|
||||||
|
for (const entry of currentArchiveItems) {
|
||||||
|
if (!isExtractedLabel(entry.fullStatus)) {
|
||||||
|
entry.fullStatus = "Entpackt";
|
||||||
|
entry.updatedAt = doneAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastHybridArchiveName = progress.archiveName;
|
||||||
|
const resolved = resolveArchiveItems(progress.archiveName);
|
||||||
|
currentArchiveItems = resolved.length > 0 ? resolved : hybridItems;
|
||||||
}
|
}
|
||||||
const archive = progress.archiveName ? ` · ${progress.archiveName}` : "";
|
const archive = progress.archiveName ? ` · ${progress.archiveName}` : "";
|
||||||
const elapsed = progress.elapsedMs && progress.elapsedMs >= 1000
|
const elapsed = progress.elapsedMs && progress.elapsedMs >= 1000
|
||||||
@ -4577,24 +4601,33 @@ export class DownloadManager extends EventEmitter {
|
|||||||
pkg.status = "extracting";
|
pkg.status = "extracting";
|
||||||
this.emitState();
|
this.emitState();
|
||||||
|
|
||||||
// Build map: archive basename -> items belonging to that archive set
|
// Resolve items belonging to a specific archive entry point by filename pattern matching
|
||||||
const archiveToItems = new Map<string, DownloadItem[]>();
|
const resolveArchiveItems = (archiveName: string): DownloadItem[] => {
|
||||||
let dirFiles: string[] | undefined;
|
const entryLower = archiveName.toLowerCase();
|
||||||
try {
|
const multipartMatch = entryLower.match(/^(.*)\.part0*1\.rar$/);
|
||||||
dirFiles = fs.readdirSync(pkg.outputDir, { withFileTypes: true })
|
if (multipartMatch) {
|
||||||
.filter((entry) => entry.isFile())
|
const prefix = multipartMatch[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
.map((entry) => entry.name);
|
const pattern = new RegExp(`^${prefix}\\.part\\d+\\.rar$`, "i");
|
||||||
} catch { /* ignore */ }
|
return completedItems.filter((item) => {
|
||||||
const candidates = findArchiveCandidates(pkg.outputDir);
|
const name = path.basename(item.targetPath || item.fileName || "");
|
||||||
for (const candidate of candidates) {
|
return pattern.test(name);
|
||||||
const parts = collectArchiveCleanupTargets(candidate, dirFiles);
|
});
|
||||||
const partKeys = new Set(parts.map((p) => pathKey(p)));
|
|
||||||
partKeys.add(pathKey(candidate));
|
|
||||||
const matched = completedItems.filter((item) => item.targetPath && partKeys.has(pathKey(item.targetPath)));
|
|
||||||
if (matched.length > 0) {
|
|
||||||
archiveToItems.set(path.basename(candidate).toLowerCase(), matched);
|
|
||||||
}
|
}
|
||||||
|
// Single-file archive or non-multipart RAR: match based on archive stem
|
||||||
|
const rarMatch = entryLower.match(/^(.*)\.rar$/);
|
||||||
|
if (rarMatch) {
|
||||||
|
const stem = rarMatch[1].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const pattern = new RegExp(`^${stem}\\.r(ar|\\d{2,3})$`, "i");
|
||||||
|
return completedItems.filter((item) => {
|
||||||
|
const name = path.basename(item.targetPath || item.fileName || "");
|
||||||
|
return pattern.test(name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return completedItems.filter((item) => {
|
||||||
|
const name = path.basename(item.targetPath || item.fileName || "").toLowerCase();
|
||||||
|
return name === entryLower;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
let currentArchiveItems: DownloadItem[] = completedItems;
|
let currentArchiveItems: DownloadItem[] = completedItems;
|
||||||
const updateExtractingStatus = (text: string): void => {
|
const updateExtractingStatus = (text: string): void => {
|
||||||
@ -4618,6 +4651,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
let lastExtractStatusText = "";
|
let lastExtractStatusText = "";
|
||||||
let lastExtractEmitAt = 0;
|
let lastExtractEmitAt = 0;
|
||||||
|
let lastExtractArchiveName = "";
|
||||||
const emitExtractStatus = (text: string, force = false): void => {
|
const emitExtractStatus = (text: string, force = false): void => {
|
||||||
updateExtractingStatus(text);
|
updateExtractingStatus(text);
|
||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
@ -4668,9 +4702,20 @@ export class DownloadManager extends EventEmitter {
|
|||||||
signal: extractAbortController.signal,
|
signal: extractAbortController.signal,
|
||||||
packageId,
|
packageId,
|
||||||
onProgress: (progress) => {
|
onProgress: (progress) => {
|
||||||
// Narrow status updates to only items belonging to the current archive
|
// When a new archive starts, mark the previous archive's items as "Entpackt"
|
||||||
if (progress.archiveName) {
|
if (progress.archiveName && progress.archiveName !== lastExtractArchiveName) {
|
||||||
currentArchiveItems = archiveToItems.get(progress.archiveName.toLowerCase()) || completedItems;
|
if (lastExtractArchiveName && currentArchiveItems !== completedItems) {
|
||||||
|
const doneAt = nowMs();
|
||||||
|
for (const entry of currentArchiveItems) {
|
||||||
|
if (!isExtractedLabel(entry.fullStatus)) {
|
||||||
|
entry.fullStatus = "Entpackt";
|
||||||
|
entry.updatedAt = doneAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastExtractArchiveName = progress.archiveName;
|
||||||
|
const resolved = resolveArchiveItems(progress.archiveName);
|
||||||
|
currentArchiveItems = resolved.length > 0 ? resolved : completedItems;
|
||||||
}
|
}
|
||||||
const label = progress.phase === "done"
|
const label = progress.phase === "done"
|
||||||
? "Entpacken 100%"
|
? "Entpacken 100%"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user