# Intensive Analyse: Pausen zwischen Pack-Entpackungen (10–15 Sekunden) **Nur Analyse – keine Code-Änderungen.** --- ## 1. Problem Nach dem Entpacken eines Packs (z.B. 3 Parts einer Serie) passiert ca. 10–15 Sekunden lang scheinbar nichts, bevor das nächste Pack mit dem Entpacken beginnt. --- ## 2. Steuerungslogik: Ein Slot für alle Packs - **Nur ein Pack** darf gleichzeitig Post-Processing (inkl. Entpacken) machen. - Steuerung: `acquirePostProcessSlot(packageId)` / `releasePostProcessSlot()` in `download-manager.ts`. - Weitere Packs warten in `packagePostProcessWaiters` und kommen erst dran, wenn der aktive Task im **finally**-Block `releasePostProcessSlot()` aufruft. ```3761:3804:src/main/download-manager.ts private async acquirePostProcessSlot(packageId: string): Promise { const maxConcurrent = 1; // ... } private releasePostProcessSlot(): void { // ... } ``` - Der Slot wird **erst** freigegeben, wenn die gesamte `runPackagePostProcessing`-Task-Funktion durch ist – genauer: wenn ihr **finally**-Block läuft (dort `releasePostProcessSlot()`). Alles, was vorher im gleichen Task **synchron** (await) läuft, blockiert den Slot und damit das nächste Pack. --- ## 3. Zwei relevante Code-Pfade ### 3.1 Pfad A: Hybrid-Extract (Pack noch nicht fertig) - Bedingung: `!allDone && settings.hybridExtract && autoExtract && failed === 0 && success > 0`. - Es werden nur die **bereits fertigen** Archive des Packs entpackt (`onlyArchives: readyArchives`), mit `skipPostCleanup: true` (kein Post-Cleanup im Extractor). - Ablauf: 1. `handlePackagePostProcessing` → `runHybridExtraction`. 2. `await extractPackageArchives(..., onlyArchives, skipPostCleanup: true)`. 3. **Direkt danach (im gleichen Callstack, vor Rückkehr):** `await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg)` (Zeile 6490). 4. Dann return aus `handlePackagePostProcessing` → **finally** → `releasePostProcessSlot()`. **Folge:** Im Hybrid-Pfad blockiert **Auto-Rename** den Slot. Solange Rename läuft (rekursives Scannen + Umbenennen), kann das nächste Pack nicht starten. Das kann gut 10–15 Sekunden ausmachen. --- ### 3.2 Pfad B: Finales Post-Processing (Pack komplett, alle Items fertig) - Bedingung: `allDone` (alle Items completed/failed/cancelled). - Es wird das **gesamte** Pack entpackt (`extractPackageArchives` ohne `onlyArchives`), **ohne** `skipPostCleanup`. - Ablauf: 1. `await extractPackageArchives(...)` – **inklusive allem, was der Extractor danach noch macht** (siehe Abschnitt 4). 2. Status-Updates, `recordPackageHistory(...)` (synchron, schnell). 3. `void this.runDeferredPostExtraction(...)` – wird **nicht** awaitet; Rename, MKV-Sammlung, Cleanup laufen im Hintergrund. 4. `handlePackagePostProcessing` kehrt zurück → **finally** → `releasePostProcessSlot()`. **Folge:** Im Final-Pfad blockieren **nicht** mehr Rename/MKV/Cleanup im Download-Manager den Slot – die sind in `runDeferredPostExtraction` ausgelagert. Was den Slot aber **noch** blockiert, ist alles, was **innerhalb** von `extractPackageArchives` **nach** dem eigentlichen Entpacken passiert (Post-Cleanup und ggf. Nested-Extraction im Extractor). --- ## 4. Was passiert INNERHALB von `extractPackageArchives` (Extractor) – und blockiert Nach dem Durchlauf über alle Kandidaten-Archive folgt im Extractor (`extractor.ts`) noch: ### 4.1 Nested-Extraction (Zeilen 2208–2284) - Wenn `extracted > 0 && !skipPostCleanup && !onlyArchives`: Es werden Archive **im Zielordner** gesucht (`findArchiveCandidates(options.targetDir)`) und nacheinander entpackt. - Pro nested-Archiv: Entpacken, ggf. `cleanupArchives([nestedArchive], ...)`. - Kann bei vielen/vollen Archiven deutlich Zeit kosten und den Slot blockieren. ### 4.2 Post-Cleanup (Zeilen 2286–2328) - Nur wenn `!options.skipPostCleanup`: - **cleanupArchives(cleanupSources, cleanupMode):** Entfernen/Trash der entpackten Quell-Archive (readdir pro Verzeichnis, ggf. viele `rm`/rename). - **removeDownloadLinkArtifacts(targetDir):** Link-Artefakte im Zielordner entfernen. - **removeSampleArtifacts(targetDir):** Rekursives Durchlaufen des kompletten Extract-Ordners, Erkennung von Sample-Dateien/Ordnern, Löschen. - **removeEmptyDirectoryTree(packageDir):** Rekursives Auflisten aller Unterordner, dann sortiert leere Ordner von tief nach flach löschen. All das läuft **vor** dem Return von `extractPackageArchives`. Erst danach kommt im Download-Manager noch `recordPackageHistory` und `void runDeferredPostExtraction`. Der Slot wird also erst nach dem **gesamten** `extractPackageArchives`-Lauf (inkl. Nested + Post-Cleanup) freigegeben. **Typische Zeitfresser (10–15 s):** - `cleanupArchives`: viele Dateien/Archive → viele I/O-Ops. - `removeSampleArtifacts`: vollständiger rekursiver Scan des Extract-Ordners. - `removeEmptyDirectoryTree`: rekursives readdir über die ganze Verzeichnisstruktur. - Nested-Extraction: zusätzliches Entpacken und ggf. weiteres Cleanup. --- ## 5. Was im Download-Manager NACH dem Extractor noch passiert (Final-Pfad) - **recordPackageHistory:** synchron, in-memory + Callback – vernachlässigbar. - **runDeferredPostExtraction:** wird mit `void` gestartet, blockiert den Slot **nicht**. Darin laufen (im Hintergrund): - `autoRenameExtractedVideoFiles` - `cleanupRemainingArchiveArtifacts` (bei Hybrid-Szenario/cleanupMode) - `collectMkvFilesToLibrary` - `applyPackageDoneCleanup` Diese Schritte verursachen **keine** Pause mehr zwischen zwei Packs im Final-Pfad, weil der Slot schon vorher freigegeben wird. --- ## 6. Zusammenfassung: Wo entstehen die 10–15 Sekunden Pause? | Szenario | Was blockiert den Slot (Pause bis zum nächsten Pack)? | |----------|--------------------------------------------------------| | **Hybrid-Extract** (Pack hat noch offene Items) | `await autoRenameExtractedVideoFiles` **direkt nach** `extractPackageArchives` in `runHybridExtraction` (Zeile 6490). Rekursives Scannen + Umbenennen aller Video-Dateien. | | **Finales Post-Processing** (Pack fertig) | Alles **innerhalb** von `extractPackageArchives`: Nested-Extraction (falls vorhanden) + Post-Cleanup (`cleanupArchives`, `removeDownloadLinkArtifacts`, `removeSampleArtifacts`, `removeEmptyDirectoryTree`). Rekursive Scans und viele I/O-Ops. | In beiden Fällen ist die Pause also die Zeit **vor** `releasePostProcessSlot()` – einmal durch Rename im Manager (Hybrid), einmal durch Post-Cleanup und Nested-Extraction im Extractor (Final). --- ## 7. Mögliche Verbesserungen (nur Konzept, keine Änderung) - **Hybrid-Pfad:** `autoRenameExtractedVideoFiles` nach dem Hybrid-Extract **nicht** mehr awaiten, sondern (analog zu `runDeferredPostExtraction`) im Hintergrund starten und sofort aus `runHybridExtraction` zurückkehren. Dann wird der Slot direkt nach `extractPackageArchives` freigegeben; Rename läuft parallel. - **Final-Pfad / Extractor:** Post-Cleanup (und ggf. Nested-Extraction) **nicht** mehr synchron am Ende von `extractPackageArchives` ausführen, sondern: - Entweder: Extractor gibt nach dem letzten „eigentlichen“ Entpacken sofort zurück und eine andere Komponente (z. B. Download-Manager oder eine Queue) übernimmt Cleanup/Nested im Hintergrund; oder - Extractor bekommt eine Option (z. B. `deferPostCleanup: true`), liefert die nötigen Daten (z. B. Liste der zu löschenden Archive) zurück, und der Aufrufer führt Cleanup/Nested asynchron aus. - **Slot-Logik unverändert:** Ein Slot bleibt sinnvoll, um I/O und CPU beim Entpacken zu bündeln. Durch die Entkopplung der „teuren“ Schritte (Rename, Cleanup, Nested) von der Slot-Holding-Zeit verkürzt sich die Pause zwischen zwei Packs ohne Parallel-Entpacken mehrerer Packs. --- ## 8. Relevante Stellen im Code (Orientierung) - Slot: `acquirePostProcessSlot` / `releasePostProcessSlot` (download-manager.ts, ca. 3761–3804). - Post-Processing-Task: `runPackagePostProcessing` → `handlePackagePostProcessing` (ca. 3806–3854, 6544–6916). - Hybrid: `runHybridExtraction` (ca. 6374–6542), inkl. `await autoRenameExtractedVideoFiles` (6490). - Final: `handlePackagePostProcessing` nach `extractPackageArchives` (6697–6916): `recordPackageHistory`, `void runDeferredPostExtraction`, dann return. - Extractor: `extractPackageArchives` (extractor.ts, ca. 1880–2353), Nested 2208–2284, Post-Cleanup 2286–2328. - Rename: `autoRenameExtractedVideoFiles` (download-manager.ts, 2173–2312), nutzt `collectVideoFiles` (rekursiv). - MKV/Cleanup: `collectMkvFilesToLibrary` (2448), `cleanupRemainingArchiveArtifacts` (2353), `runDeferredPostExtraction` (6922–6965). --- ## 9. Vergleich: JDownloader (jdownloader-source) Im JDownloader-Quellcode (z. B. `C:\Users\ploet\Desktop\jdownloader-source`) ist das Entpacken so aufgebaut, dass **pro Pack (3 Parts = 1 Folge) kaum schwere Arbeit nach dem eigentlichen Entpacken** im gleichen Queue-Job läuft – deshalb wirkt es „ohne Pause“. ### Ablauf bei JDownloader - **Ein Archiv = ein Pack** (z. B. 3 RAR-Parts = 1 Archive mit `archive.getArchiveFiles()`). - **Eine Queue** (`ExtractionQueue`), ein Job pro Archiv (`ExtractionController` extends `QueueAction`). - Pro Job passiert in `ExtractionController.run()`: 1. `extractor.extract(this)` – reines Entpacken. 2. `extractor.close()`. 3. Je nach Exit-Code: `fireEvent(ExtractionEvent.Type.FINISHED)` (inkl. `FileCreationEvent(NEW_FILES, files)` – die Dateiliste kommt vom Extractor, **kein** rekursives Scannen). 4. Im **finally**: `fireEvent(Type.CLEANUP)` → `archive.onCleanUp()`. 5. Listener bei `CLEANUP`: `controller.removeArchiveFiles()`. ### Was `removeArchiveFiles()` bei JDownloader macht - Holt die **bereits bekannten** Archive-Dateien: `archive.getArchiveFiles()` (die 3 Parts sind dem Archiv von Anfang an zugeordnet). - Löscht nur diese Dateien (z. B. `link.deleteFile(remove)` pro Part). - **Kein** rekursives Durchsuchen von Ordnern, **kein** `findArchiveCandidates`, **kein** Scannen des Extract-Ordners. - Aufwand: O(Anzahl Parts) Datei-Löschungen, typisch sehr schnell. ### Was JDownloader in diesem Pfad nicht macht - **Kein** Auto-Rename der entpackten Dateien im Extraction-Queue-Job (LinknameCleaner wird an anderer Stelle für Pfadsegmente genutzt, nicht als Blockierung nach Extract). - **Kein** „Collect MKV to Library“ (rekursives Scannen + Verschieben) im gleichen Job. - **Kein** `removeSampleArtifacts` (rekursiver Scan des Extract-Ordners). - **Kein** `removeEmptyDirectoryTree` (rekursives Auflisten aller Unterordner). - Nested-Archive (Deep-Extraction) werden als **neue** Archive in die Queue gestellt (`addToQueue(..., newArchive, false)`), also **separate Jobs**, die nacheinander laufen – der aktuelle Job ist sofort fertig. ### Warum es sich „flawless“ anfühlt - Der kritische Pfad pro Pack ist: **Entpacken → Event FINISHED → Event CLEANUP → nur die bekannten Archive-Dateien löschen → `run()` endet**. - Keine rechen- oder I/O-intensiven Schritte (keine rekursiven Scans, kein Rename, keine MKV-Sammlung) im gleichen Queue-Job. - Das nächste Pack (nächster `ExtractionController` in der Queue) startet direkt nach `run()` return – die spürbare Pause entfällt. ### Übertrag auf unser Projekt - Um ein ähnlich „flüssiges“ Verhalten zu erreichen, sollten **alle** zeitaufwändigen Schritte (Rename, MKV-Sammlung, Sample-Cleanup, leere Ordner entfernen, ggf. Post-Cleanup im Extractor) **nicht** den Post-Process-Slot blockieren. - Konkret: Sie entweder **nach** `releasePostProcessSlot()` im Hintergrund ausführen (wie beim Final-Pfad bereits für Rename/MKV/Cleanup im Manager) **oder** den Extractor so auslegen, dass er direkt nach dem letzten eigentlichen Entpacken zurückkehrt und Cleanup/Nested in einem separaten, asynchronen Schritt erledigt wird (siehe Abschnitt 7).