Refactor: Extractor in 18 Sektionen reorganisiert

This commit is contained in:
Sucukdeluxe 2026-03-10 23:47:02 +01:00
parent b49de16534
commit d9170f4167
25 changed files with 114170 additions and 30695 deletions

View File

@ -0,0 +1,183 @@
# Intensive Analyse: Pausen zwischen Pack-Entpackungen (1015 Sekunden)
**Nur Analyse keine Code-Änderungen.**
---
## 1. Problem
Nach dem Entpacken eines Packs (z.B. 3 Parts einer Serie) passiert ca. 1015 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<void> {
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 1015 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 22082284)
- 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 22862328)
- 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 (1015 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 1015 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. 37613804).
- Post-Processing-Task: `runPackagePostProcessing``handlePackagePostProcessing` (ca. 38063854, 65446916).
- Hybrid: `runHybridExtraction` (ca. 63746542), inkl. `await autoRenameExtractedVideoFiles` (6490).
- Final: `handlePackagePostProcessing` nach `extractPackageArchives` (66976916): `recordPackageHistory`, `void runDeferredPostExtraction`, dann return.
- Extractor: `extractPackageArchives` (extractor.ts, ca. 18802353), Nested 22082284, Post-Cleanup 22862328.
- Rename: `autoRenameExtractedVideoFiles` (download-manager.ts, 21732312), nutzt `collectVideoFiles` (rekursiv).
- MKV/Cleanup: `collectMkvFilesToLibrary` (2448), `cleanupRemainingArchiveArtifacts` (2353), `runDeferredPostExtraction` (69226965).
---
## 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).

79822
rd_downloader.log.old Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3564
src/main/extractor.ts.bak Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff