Release v1.6.25
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1854e6bb17
commit
940346e2f4
+1
-1
@@ -317,7 +317,7 @@ async function runWithConcurrency<T>(items: T[], concurrency: number, worker: (i
|
||||
let index = 0;
|
||||
let firstError: unknown = null;
|
||||
const next = (): T | undefined => {
|
||||
if (index >= items.length) {
|
||||
if (firstError || index >= items.length) {
|
||||
return undefined;
|
||||
}
|
||||
const item = items[index];
|
||||
|
||||
@@ -2669,6 +2669,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
|
||||
public skipItems(itemIds: string[]): void {
|
||||
const affectedPackageIds = new Set<string>();
|
||||
for (const itemId of itemIds) {
|
||||
const item = this.session.items[itemId];
|
||||
if (!item) continue;
|
||||
@@ -2680,6 +2681,11 @@ export class DownloadManager extends EventEmitter {
|
||||
this.retryAfterByItem.delete(itemId);
|
||||
this.retryStateByItem.delete(itemId);
|
||||
this.recordRunOutcome(itemId, "cancelled");
|
||||
affectedPackageIds.add(item.packageId);
|
||||
}
|
||||
for (const pkgId of affectedPackageIds) {
|
||||
const pkg = this.session.packages[pkgId];
|
||||
if (pkg) this.refreshPackageStatus(pkg);
|
||||
}
|
||||
this.persistSoon();
|
||||
this.emitState();
|
||||
@@ -3328,13 +3334,17 @@ export class DownloadManager extends EventEmitter {
|
||||
pkg.itemIds = pkg.itemIds.filter((id) => id !== itemId);
|
||||
this.releaseTargetPath(itemId);
|
||||
this.dropItemContribution(itemId);
|
||||
delete this.session.items[itemId];
|
||||
this.itemCount = Math.max(0, this.itemCount - 1);
|
||||
this.retryAfterByItem.delete(itemId);
|
||||
this.retryStateByItem.delete(itemId);
|
||||
removed += 1;
|
||||
}
|
||||
if (pkg.itemIds.length === 0) {
|
||||
this.removePackageFromSession(pkgId, []);
|
||||
this.removePackageFromSession(pkgId, completedItemIds);
|
||||
} else {
|
||||
for (const itemId of completedItemIds) {
|
||||
delete this.session.items[itemId];
|
||||
this.itemCount = Math.max(0, this.itemCount - 1);
|
||||
}
|
||||
}
|
||||
} else if (policy === "package_done" || policy === "on_start") {
|
||||
const allCompleted = pkg.itemIds.every((id) => {
|
||||
@@ -6301,7 +6311,8 @@ export class DownloadManager extends EventEmitter {
|
||||
logger.warn(`Hybrid-Extract Fehler: pkg=${pkg.name}, reason=${compactErrorText(error)}`);
|
||||
const errorAt = nowMs();
|
||||
for (const entry of hybridItems) {
|
||||
if (entry.fullStatus === "Entpacken - Ausstehend" || entry.fullStatus === "Entpacken - Warten auf Parts") {
|
||||
if (isExtractedLabel(entry.fullStatus || "")) continue;
|
||||
if (/^Entpacken\b/i.test(entry.fullStatus || "") || entry.fullStatus === "Entpacken - Ausstehend" || entry.fullStatus === "Entpacken - Warten auf Parts") {
|
||||
entry.fullStatus = `Entpacken - Error`;
|
||||
entry.updatedAt = errorAt;
|
||||
}
|
||||
@@ -6838,7 +6849,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const paused = this.session.running && this.session.paused;
|
||||
const currentSpeedBps = paused ? 0 : this.speedBytesLastWindow / SPEED_WINDOW_SECONDS;
|
||||
const currentSpeedBps = !this.session.running || paused ? 0 : this.speedBytesLastWindow / SPEED_WINDOW_SECONDS;
|
||||
|
||||
let maxSpeed = 0;
|
||||
for (let i = this.speedEventsHead; i < this.speedEvents.length; i += 1) {
|
||||
|
||||
+13
-2
@@ -1674,6 +1674,11 @@ export function collectArchiveCleanupTargets(sourceArchivePath: string, director
|
||||
return Array.from(targets);
|
||||
}
|
||||
|
||||
// Tar compound archives (.tar.gz, .tar.bz2, .tar.xz, .tgz, .tbz2, .txz)
|
||||
if (/\.(?:tar\.(?:gz|bz2|xz)|tgz|tbz2|txz)$/i.test(fileName)) {
|
||||
return Array.from(targets);
|
||||
}
|
||||
|
||||
// Generic .NNN split files (HJSplit etc.)
|
||||
const genericSplit = fileName.match(/^(.*)\.(\d{3})$/i);
|
||||
if (genericSplit) {
|
||||
@@ -1994,6 +1999,8 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
||||
if (!sig) {
|
||||
logger.info(`Generische Split-Datei übersprungen (keine Archiv-Signatur): ${archiveName}`);
|
||||
extracted += 1;
|
||||
resumeCompleted.add(archiveResumeKey);
|
||||
extractedArchives.push(archivePath);
|
||||
clearInterval(pulseTimer);
|
||||
return;
|
||||
}
|
||||
@@ -2127,8 +2134,12 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
||||
try {
|
||||
await extractSingleArchive(queue[idx]);
|
||||
} catch (error) {
|
||||
if (isExtractAbortError(String(error))) {
|
||||
abortError = error instanceof Error ? error : new Error(String(error));
|
||||
const errText = String(error);
|
||||
if (errText.includes("noextractor:skipped")) {
|
||||
break; // handled by noExtractorEncountered flag after the pool
|
||||
}
|
||||
if (isExtractAbortError(errText)) {
|
||||
abortError = error instanceof Error ? error : new Error(errText);
|
||||
break;
|
||||
}
|
||||
// Non-abort errors are already handled inside extractSingleArchive
|
||||
|
||||
+17
-3
@@ -548,6 +548,7 @@ export function loadSession(paths: StoragePaths): SessionState {
|
||||
}
|
||||
|
||||
export function saveSession(paths: StoragePaths, session: SessionState): void {
|
||||
syncSaveGeneration += 1;
|
||||
ensureBaseDir(paths.baseDir);
|
||||
if (fs.existsSync(paths.sessionFile)) {
|
||||
try {
|
||||
@@ -569,16 +570,26 @@ export function saveSession(paths: StoragePaths, session: SessionState): void {
|
||||
|
||||
let asyncSaveRunning = false;
|
||||
let asyncSaveQueued: { paths: StoragePaths; payload: string } | null = null;
|
||||
let syncSaveGeneration = 0;
|
||||
|
||||
async function writeSessionPayload(paths: StoragePaths, payload: string): Promise<void> {
|
||||
async function writeSessionPayload(paths: StoragePaths, payload: string, generation: number): Promise<void> {
|
||||
await fs.promises.mkdir(paths.baseDir, { recursive: true });
|
||||
await fsp.copyFile(paths.sessionFile, sessionBackupPath(paths.sessionFile)).catch(() => {});
|
||||
const tempPath = sessionTempPath(paths.sessionFile, "async");
|
||||
await fsp.writeFile(tempPath, payload, "utf8");
|
||||
// If a synchronous save occurred after this async save started, discard the stale write
|
||||
if (generation < syncSaveGeneration) {
|
||||
await fsp.rm(tempPath, { force: true }).catch(() => {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fsp.rename(tempPath, paths.sessionFile);
|
||||
} catch (renameError: unknown) {
|
||||
if (renameError && typeof renameError === "object" && "code" in renameError && (renameError as NodeJS.ErrnoException).code === "EXDEV") {
|
||||
if (generation < syncSaveGeneration) {
|
||||
await fsp.rm(tempPath, { force: true }).catch(() => {});
|
||||
return;
|
||||
}
|
||||
await fsp.copyFile(tempPath, paths.sessionFile);
|
||||
await fsp.rm(tempPath, { force: true }).catch(() => {});
|
||||
} else {
|
||||
@@ -593,8 +604,9 @@ async function saveSessionPayloadAsync(paths: StoragePaths, payload: string): Pr
|
||||
return;
|
||||
}
|
||||
asyncSaveRunning = true;
|
||||
const gen = syncSaveGeneration;
|
||||
try {
|
||||
await writeSessionPayload(paths, payload);
|
||||
await writeSessionPayload(paths, payload, gen);
|
||||
} catch (error) {
|
||||
logger.error(`Async Session-Save fehlgeschlagen: ${String(error)}`);
|
||||
} finally {
|
||||
@@ -610,6 +622,7 @@ async function saveSessionPayloadAsync(paths: StoragePaths, payload: string): Pr
|
||||
export function cancelPendingAsyncSaves(): void {
|
||||
asyncSaveQueued = null;
|
||||
asyncSettingsSaveQueued = null;
|
||||
syncSaveGeneration += 1;
|
||||
}
|
||||
|
||||
export async function saveSessionAsync(paths: StoragePaths, session: SessionState): Promise<void> {
|
||||
@@ -637,7 +650,8 @@ function normalizeHistoryEntry(raw: unknown, index: number): HistoryEntry | null
|
||||
completedAt: clampNumber(entry.completedAt, Date.now(), 0, Number.MAX_SAFE_INTEGER),
|
||||
durationSeconds: clampNumber(entry.durationSeconds, 0, 0, Number.MAX_SAFE_INTEGER),
|
||||
status: entry.status === "deleted" ? "deleted" : "completed",
|
||||
outputDir: asText(entry.outputDir)
|
||||
outputDir: asText(entry.outputDir),
|
||||
urls: Array.isArray(entry.urls) ? (entry.urls as unknown[]).map(String).filter(Boolean) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user