diff --git a/package.json b/package.json index 335bd27..1a91134 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.4.8", + "version": "1.4.9", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 90f99b3..12f597a 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -969,6 +969,8 @@ export class DownloadManager extends EventEmitter { this.emitState(true); } + this.triggerPendingExtractions(); + const runItems = Object.values(this.session.items) .filter((item) => { if (item.status !== "queued" && item.status !== "reconnect_wait") { @@ -1420,6 +1422,15 @@ export class DownloadManager extends EventEmitter { const needsPostProcess = pkg.status !== "completed" || items.some((item) => item.status === "completed" && !isExtractedLabel(item.fullStatus)); if (needsPostProcess) { + pkg.status = "queued"; + pkg.updatedAt = nowMs(); + for (const item of items) { + if (item.status === "completed" && !isExtractedLabel(item.fullStatus)) { + item.fullStatus = "Entpacken ausstehend"; + item.updatedAt = nowMs(); + } + } + changed = true; void this.runPackagePostProcessing(packageId); } else if (pkg.status !== "completed") { pkg.status = "completed"; @@ -1443,6 +1454,47 @@ export class DownloadManager extends EventEmitter { } } + private triggerPendingExtractions(): void { + if (!this.settings.autoExtract) { + return; + } + for (const packageId of this.session.packageOrder) { + const pkg = this.session.packages[packageId]; + if (!pkg || pkg.cancelled || !pkg.enabled) { + continue; + } + if (this.packagePostProcessTasks.has(packageId)) { + continue; + } + const items = pkg.itemIds.map((id) => this.session.items[id]).filter(Boolean) as DownloadItem[]; + if (items.length === 0) { + continue; + } + const success = items.filter((item) => item.status === "completed").length; + const failed = items.filter((item) => item.status === "failed").length; + const cancelled = items.filter((item) => item.status === "cancelled").length; + if (success + failed + cancelled < items.length || failed > 0 || success === 0) { + continue; + } + const needsExtraction = items.some((item) => + item.status === "completed" && !isExtractedLabel(item.fullStatus) + ); + if (!needsExtraction) { + continue; + } + pkg.status = "queued"; + pkg.updatedAt = nowMs(); + for (const item of items) { + if (item.status === "completed" && !isExtractedLabel(item.fullStatus)) { + item.fullStatus = "Entpacken ausstehend"; + item.updatedAt = nowMs(); + } + } + logger.info(`Entpacken via Start ausgelöst: pkg=${pkg.name}`); + void this.runPackagePostProcessing(packageId); + } + } + private removePackageFromSession(packageId: string, itemIds: string[]): void { for (const itemId of itemIds) { delete this.session.items[itemId]; diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 0e2111b..88644e0 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -571,6 +571,19 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ const candidates = findArchiveCandidates(options.packageDir); logger.info(`Entpacken gestartet: packageDir=${options.packageDir}, targetDir=${options.targetDir}, archives=${candidates.length}, cleanupMode=${options.cleanupMode}, conflictMode=${options.conflictMode}`); if (candidates.length === 0) { + const existingResume = readExtractResumeState(options.packageDir); + if (existingResume.size > 0 && hasAnyFilesRecursive(options.targetDir)) { + clearExtractResumeState(options.packageDir); + logger.info(`Entpacken übersprungen (Archive bereinigt, Ziel hat Dateien): ${options.packageDir}`); + options.onProgress?.({ + current: existingResume.size, + total: existingResume.size, + percent: 100, + archiveName: "", + phase: "done" + }); + return { extracted: existingResume.size, failed: 0, lastError: "" }; + } clearExtractResumeState(options.packageDir); logger.info(`Entpacken übersprungen (keine Archive gefunden): ${options.packageDir}`); return { extracted: 0, failed: 0, lastError: "" }; diff --git a/src/main/update.ts b/src/main/update.ts index f9b498a..c26a5f3 100644 --- a/src/main/update.ts +++ b/src/main/update.ts @@ -8,9 +8,10 @@ import { ReadableStream as NodeReadableStream } from "node:stream/web"; import { APP_VERSION, DEFAULT_UPDATE_REPO } from "./constants"; import { UpdateCheckResult, UpdateInstallResult } from "../shared/types"; import { compactErrorText } from "./utils"; +import { logger } from "./logger"; const RELEASE_FETCH_TIMEOUT_MS = 12000; -const DOWNLOAD_TIMEOUT_MS = 8 * 60 * 1000; +const CONNECT_TIMEOUT_MS = 30000; const UPDATE_USER_AGENT = `RD-Node-Downloader/${APP_VERSION}`; type ReleaseAsset = { @@ -274,13 +275,15 @@ export async function checkGitHubUpdate(repo: string): Promise { - const timeout = timeoutController(DOWNLOAD_TIMEOUT_MS); + logger.info(`Update-Download versucht: ${url}`); + const timeout = timeoutController(CONNECT_TIMEOUT_MS); let response: Response; try { response = await fetch(url, { headers: { "User-Agent": UPDATE_USER_AGENT }, + redirect: "follow", signal: timeout.signal }); } finally { @@ -294,11 +297,13 @@ async function downloadFile(url: string, targetPath: string): Promise { const source = Readable.fromWeb(response.body as unknown as NodeReadableStream); const target = fs.createWriteStream(targetPath); await pipeline(source, target); + logger.info(`Update-Download abgeschlossen: ${targetPath}`); } async function downloadFromCandidates(candidates: string[], targetPath: string): Promise { let lastError: unknown = new Error("Update Download fehlgeschlagen"); + logger.info(`Update-Download: ${candidates.length} Kandidat(en)`); for (let index = 0; index < candidates.length; index += 1) { const candidate = candidates[index]; try { @@ -306,6 +311,7 @@ async function downloadFromCandidates(candidates: string[], targetPath: string): return; } catch (error) { lastError = error; + logger.warn(`Update-Download Kandidat ${index + 1}/${candidates.length} fehlgeschlagen: ${compactErrorText(error)}`); try { await fs.promises.rm(targetPath, { force: true }); } catch { @@ -373,6 +379,8 @@ export async function installLatestUpdate(repo: string, prechecked?: UpdateCheck } catch { // ignore } - return { started: false, message: compactErrorText(error) }; + const releaseUrl = String(effectiveCheck.releaseUrl || "").trim(); + const hint = releaseUrl ? ` – Manuell: ${releaseUrl}` : ""; + return { started: false, message: `${compactErrorText(error)}${hint}` }; } }