Fix archive underflow and extraction readiness
This commit is contained in:
@@ -4007,12 +4007,15 @@ export class DownloadManager extends EventEmitter {
|
||||
* old 50% recovery threshold). Reset to "queued" so it gets re-downloaded. */
|
||||
private revalidateCompletedItems(): void {
|
||||
let fixed = 0;
|
||||
const touchedPackageIds = new Set<string>();
|
||||
for (const item of Object.values(this.session.items)) {
|
||||
if (item.status !== "completed") continue;
|
||||
if (!item.targetPath || !item.totalBytes || item.totalBytes <= 0) continue;
|
||||
try {
|
||||
const stat = fs.statSync(item.targetPath);
|
||||
if (stat.size < item.totalBytes - ALLOCATION_UNIT_SIZE) {
|
||||
const expectedMinSize = item.totalBytes - ALLOCATION_UNIT_SIZE;
|
||||
const persistedShortfall = item.downloadedBytes < expectedMinSize && stat.size >= expectedMinSize;
|
||||
if (stat.size < expectedMinSize) {
|
||||
logger.warn(`revalidateCompleted: ${item.fileName} ist nur ${humanSize(stat.size)} statt ${humanSize(item.totalBytes)}, setze auf queued`);
|
||||
item.status = "queued";
|
||||
item.fullStatus = "Wartet";
|
||||
@@ -4020,6 +4023,15 @@ export class DownloadManager extends EventEmitter {
|
||||
item.progressPercent = Math.floor((stat.size / item.totalBytes) * 100);
|
||||
item.speedBps = 0;
|
||||
fixed += 1;
|
||||
touchedPackageIds.add(item.packageId);
|
||||
} else if (persistedShortfall) {
|
||||
logger.warn(`revalidateCompleted: ${item.fileName} wirkt pre-alloc/unvollständig (stat=${humanSize(stat.size)}, bytes=${humanSize(item.downloadedBytes)}, total=${humanSize(item.totalBytes)}), setze auf queued`);
|
||||
item.status = "queued";
|
||||
item.fullStatus = "Wartet (Auto-Recovery: pre-alloc)";
|
||||
item.progressPercent = Math.max(0, Math.min(99, Math.floor((Math.max(0, item.downloadedBytes) / item.totalBytes) * 100)));
|
||||
item.speedBps = 0;
|
||||
fixed += 1;
|
||||
touchedPackageIds.add(item.packageId);
|
||||
}
|
||||
} catch {
|
||||
// file doesn't exist — reset to queued so it gets re-downloaded
|
||||
@@ -4030,9 +4042,16 @@ export class DownloadManager extends EventEmitter {
|
||||
item.progressPercent = 0;
|
||||
item.speedBps = 0;
|
||||
fixed += 1;
|
||||
touchedPackageIds.add(item.packageId);
|
||||
}
|
||||
}
|
||||
if (fixed > 0) {
|
||||
for (const packageId of touchedPackageIds) {
|
||||
const pkg = this.session.packages[packageId];
|
||||
if (pkg) {
|
||||
this.refreshPackageStatus(pkg);
|
||||
}
|
||||
}
|
||||
logger.info(`revalidateCompletedItems: ${fixed} Items korrigiert`);
|
||||
this.persistSoon();
|
||||
}
|
||||
@@ -6530,6 +6549,23 @@ export class DownloadManager extends EventEmitter {
|
||||
throw new Error(`Download zu klein (${written} B) – Hoster-Fehlerseite?${snippet ? ` Inhalt: "${snippet}"` : ""}`);
|
||||
}
|
||||
|
||||
const exactLengthRequired = isLargeBinaryLikePath(item.fileName || effectiveTargetPath);
|
||||
if (item.totalBytes && item.totalBytes > 0 && written < item.totalBytes) {
|
||||
const shortfall = item.totalBytes - written;
|
||||
if (preAllocated) {
|
||||
try {
|
||||
await fs.promises.truncate(effectiveTargetPath, written);
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
logger.warn(`Download-Underflow: erwartet=${item.totalBytes}, erhalten=${written}, shortfall=${shortfall} fuer ${item.fileName}`);
|
||||
if (exactLengthRequired || shortfall > ALLOCATION_UNIT_SIZE) {
|
||||
item.downloadedBytes = written;
|
||||
item.progressPercent = Math.max(0, Math.min(99, Math.floor((written / item.totalBytes) * 100)));
|
||||
item.speedBps = 0;
|
||||
throw new Error(`download_underflow:${written}/${item.totalBytes}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate pre-allocated files to actual bytes written to prevent zero-padded tail
|
||||
if (preAllocated && item.totalBytes && written < item.totalBytes) {
|
||||
try {
|
||||
|
||||
+11
-4
@@ -536,8 +536,8 @@ export function classifyExtractionError(errorText: string): ExtractErrorCategory
|
||||
const text = String(errorText || "").toLowerCase();
|
||||
if (text.includes("aborted:extract") || text.includes("extract_aborted")) return "aborted";
|
||||
if (text.includes("timeout")) return "timeout";
|
||||
if (text.includes("wrong password") || text.includes("falsches passwort") || text.includes("incorrect password")) return "wrong_password";
|
||||
if (text.includes("crc failed") || text.includes("checksum error") || text.includes("crc error")) return "crc_error";
|
||||
if (text.includes("wrong password") || text.includes("falsches passwort") || text.includes("incorrect password")) return "wrong_password";
|
||||
if (text.includes("missing volume") || text.includes("next volume") || text.includes("unexpected end of archive") || text.includes("missing parts")) return "missing_parts";
|
||||
if (text.includes("nicht gefunden") || text.includes("not found") || text.includes("no extractor")) return "no_extractor";
|
||||
if (text.includes("kein rar-archiv") || text.includes("not a rar archive") || text.includes("unsupported") || text.includes("unsupportedmethod")) return "unsupported_format";
|
||||
@@ -937,9 +937,12 @@ type JvmExtractResult = {
|
||||
backend: string;
|
||||
};
|
||||
|
||||
function extractorBackendMode(): ExtractBackendMode {
|
||||
const defaultMode = "legacy";
|
||||
const raw = String(process.env.RD_EXTRACT_BACKEND || defaultMode).trim().toLowerCase();
|
||||
export function resolveExtractorBackendMode(
|
||||
rawValue?: string | null,
|
||||
isVitestEnv = Boolean(process.env.VITEST)
|
||||
): ExtractBackendMode {
|
||||
const defaultMode: ExtractBackendMode = isVitestEnv ? "legacy" : "auto";
|
||||
const raw = String(rawValue ?? defaultMode).trim().toLowerCase();
|
||||
if (raw === "legacy") {
|
||||
return "legacy";
|
||||
}
|
||||
@@ -949,6 +952,10 @@ function extractorBackendMode(): ExtractBackendMode {
|
||||
return "auto";
|
||||
}
|
||||
|
||||
function extractorBackendMode(): ExtractBackendMode {
|
||||
return resolveExtractorBackendMode(process.env.RD_EXTRACT_BACKEND);
|
||||
}
|
||||
|
||||
function isJvmRuntimeMissingError(errorText: string): boolean {
|
||||
const text = String(errorText || "").toLowerCase();
|
||||
return text.includes("could not find or load main class")
|
||||
|
||||
Reference in New Issue
Block a user