fix(v5): Serien über mehrere Discs — nur die eigenen Folgen benennen, nie überschreiben; HandBrake schreibt auf Zwischennamen
WAS: - episodenUmbenennen bekommt die Dateien DIESER Disc (nurDiese) statt den ganzen Season-Ordner zu zählen. Vorher hieß es bei Disc 2 „über- sprungen: 4 Dateien, 2 Zuordnungen", sobald Disc 1 dort schon S01E01/ S01E02 abgelegt hatte — Serien-Merker und „ab Folge" liefen ins Leere. - freierName: eine vorhandene Datei gleichen Namens im Ziel wird nie ersetzt („title_t00 (2).mkv") — außer beim Wiederholen, wo die alte Fassung ausdrücklich weicht (auftrag.ersetzt). - komprimieren() lässt HandBrake auf „<name>.teil.mkv" schreiben und benennt erst die fertige Datei um; nach Abbruch oder Fehler werden die Reste (auch eine Datei daneben) entfernt. Vorher blieb eine halbe Datei unter dem endgültigen Namen liegen — ohne Arbeitsordner direkt in der Ablage, wo Jellyfin sie als kaputten Film zeigte. WARUM: Durchsicht 12.09.2026, Funde F3 und F9 — mit Tests (Season-Ordner mit Bestand, Kollision, Zwischenname, Reste). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8c4456d005
commit
904e4b2ef6
@@ -45,3 +45,23 @@ export function rollenNummern(rollen: readonly TitelRolle[]): Array<[number, num
|
||||
return [n, gesamt.get(r) ?? 1]
|
||||
})
|
||||
}
|
||||
|
||||
/** Ein Name, der im Ordner noch frei ist (5.7.0, Durchsicht F3): `x.mkv`,
|
||||
* sonst `x (2).mkv`, `x (3).mkv` … — nie eine vorhandene Datei
|
||||
* überschreiben. Der Fall: Disc 2 einer Staffel legt `title_t00.mkv` in
|
||||
* denselben Season-Ordner, in dem Disc 1 ihr `title_t00.mkv` noch trägt
|
||||
* (Umbenennung ausgefallen) — HandBrake hätte sie still ersetzt. Pur:
|
||||
* `existiert` kommt von außen; getrennt wird am letzten Punkt des Namens,
|
||||
* Pfadtrenner beider Welten bleiben unangetastet. */
|
||||
export function freierName(pfad: string, existiert: (p: string) => boolean): string {
|
||||
if (!existiert(pfad)) return pfad
|
||||
const trenner = Math.max(pfad.lastIndexOf('/'), pfad.lastIndexOf('\\'))
|
||||
const punkt = pfad.lastIndexOf('.')
|
||||
const stamm = punkt > trenner + 1 ? pfad.slice(0, punkt) : pfad
|
||||
const endung = punkt > trenner + 1 ? pfad.slice(punkt) : ''
|
||||
for (let n = 2; n < 1000; n++) {
|
||||
const kandidat = `${stamm} (${n})${endung}`
|
||||
if (!existiert(kandidat)) return kandidat
|
||||
}
|
||||
return `${stamm} (${Date.now()})${endung}`
|
||||
}
|
||||
|
||||
@@ -84,7 +84,14 @@ export function matcheEpisoden(
|
||||
}
|
||||
|
||||
/** Benennt MKVs nach Jellyfin-Schema um: „Serie S01E02.mkv". Reihenfolge =
|
||||
* sortierte Dateinamen (MakeMKV nummeriert in Disc-Reihenfolge). */
|
||||
* sortierte Dateinamen (MakeMKV nummeriert in Disc-Reihenfolge).
|
||||
*
|
||||
* 5.7.0 (Durchsicht F3): `nurDiese` nennt die Dateien DIESER Disc (nur
|
||||
* Namen, in Ablage-Reihenfolge). Vorher zählte die Funktion ALLE MKVs im
|
||||
* Season-Ordner — und sobald Disc 1 dort schon S01E01/S01E02 abgelegt
|
||||
* hatte, hieß es bei Disc 2 „übersprungen: 4 Dateien, 2 Zuordnungen".
|
||||
* Der Serien-Merker lief damit ab der zweiten Disc ins Leere. Ohne
|
||||
* `nurDiese` gilt das alte Verhalten (alle MKVs des Ordners). */
|
||||
export function episodenUmbenennen(
|
||||
ordner: string,
|
||||
serie: string,
|
||||
@@ -99,12 +106,16 @@ export function episodenUmbenennen(
|
||||
existiert: existsSync,
|
||||
umbenennen: renameSync,
|
||||
},
|
||||
nurDiese?: readonly string[],
|
||||
): string[] {
|
||||
const meldungen: string[] = []
|
||||
const dateien = fs
|
||||
.listen(ordner)
|
||||
.filter((f) => f.endsWith('.mkv'))
|
||||
.sort()
|
||||
const dateien =
|
||||
nurDiese !== undefined
|
||||
? [...nurDiese]
|
||||
: fs
|
||||
.listen(ordner)
|
||||
.filter((f) => f.endsWith('.mkv'))
|
||||
.sort()
|
||||
if (dateien.length !== zuordnung.length) {
|
||||
return [`Umbenennen übersprungen: ${dateien.length} Dateien, ${zuordnung.length} Zuordnungen`]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
// Plattform-Pfade wie in struktur.ts: Laufzeit-Wurzeln, CI testet auf Linux.
|
||||
import { basename, join } from 'node:path'
|
||||
import type { BibliothekEintrag, KompressionStand } from '../../gemeinsam/nachrichten'
|
||||
import { rollenNummern, zielDateiname } from '../ablage/dateinamen'
|
||||
import { freierName, rollenNummern, zielDateiname } from '../ablage/dateinamen'
|
||||
import { baueNfo } from '../ablage/nfo'
|
||||
import { bibliothekRefresh } from '../ablage/medienserver'
|
||||
import { posterSpeichern } from '../ablage/poster'
|
||||
@@ -189,24 +189,35 @@ export async function komprimierenUndAblegen(
|
||||
// Laufzeiten der FOLGEN in Ablage-Reihenfolge — Grundlage der
|
||||
// Episoden-Zuordnung weiter unten.
|
||||
const folgenSekunden: number[] = []
|
||||
/** Die Folgen DIESER Disc (Zielnamen, Ablage-Reihenfolge) — nur die werden
|
||||
* umbenannt, nicht der ganze Season-Ordner (5.7.0, F3). */
|
||||
const folgenDateien: string[] = []
|
||||
// 5.6.0: Dateien heißen wie der Film (dateinamen.ts) — Folgen behalten
|
||||
// ihren Namen bis zur Episoden-Zuordnung.
|
||||
const nummern = rollenNummern(dateien.map((d) => d.rolle))
|
||||
// Beim Wiederholen darf die neue Fassung den alten Namen tragen — die alte
|
||||
// Datei fällt erst, wenn die neue fertig ist (unten). Alles andere, was
|
||||
// schon dasteht, wird NIE überschrieben (F3): dann „x (2).mkv".
|
||||
const ersetzt = new Set(auftrag.ersetzt ?? [])
|
||||
const freiOderErsetzt = (pfad: string): string => (ersetzt.has(pfad) ? pfad : freierName(pfad, existsSync))
|
||||
for (let i = 0; i < dateien.length; i++) {
|
||||
const datei = dateien[i]
|
||||
const [nummer, anzahl] = nummern[i]
|
||||
const zielDatei = join(
|
||||
zielFuer(datei.rolle),
|
||||
zielDateiname({
|
||||
titel: zuordnung?.titel ?? '',
|
||||
jahr: zuordnung?.jahr ?? null,
|
||||
alsSerie,
|
||||
rolle: datei.rolle,
|
||||
nummer,
|
||||
anzahl,
|
||||
original: basename(datei.pfad),
|
||||
}),
|
||||
const zielDatei = freiOderErsetzt(
|
||||
join(
|
||||
zielFuer(datei.rolle),
|
||||
zielDateiname({
|
||||
titel: zuordnung?.titel ?? '',
|
||||
jahr: zuordnung?.jahr ?? null,
|
||||
alsSerie,
|
||||
rolle: datei.rolle,
|
||||
nummer,
|
||||
anzahl,
|
||||
original: basename(datei.pfad),
|
||||
}),
|
||||
),
|
||||
)
|
||||
if (datei.rolle === 'folge') folgenDateien.push(basename(zielDatei))
|
||||
if (abbruch.aborted) return abgebrochen(fertigOrdner, fertigeDateien, preset, komprimiert, hinweise)
|
||||
if (!komprimiert) {
|
||||
melden({
|
||||
@@ -266,6 +277,9 @@ export async function komprimierenUndAblegen(
|
||||
audioSprachen: wahl.audio,
|
||||
untertitelSprachen: wahl.untertitel,
|
||||
untertitelStandard: wahl.untertitelStandard,
|
||||
// Beim Wiederholen darf die neue Fassung die alte gleichen Namens
|
||||
// ersetzen (ohne Arbeitsordner schreibt HandBrake direkt ins Ziel).
|
||||
ueberschreiben: zwischen.length === 0 && ersetzt.has(zielDatei),
|
||||
abbruch,
|
||||
aufFortschritt: (prozent, restS) => {
|
||||
melden({
|
||||
@@ -346,7 +360,7 @@ export async function komprimierenUndAblegen(
|
||||
episoden = laufzeiten.length > 0 ? matcheEpisoden(folgenSekunden, laufzeiten) : null
|
||||
}
|
||||
if (episoden !== null) {
|
||||
hinweise.push(...episodenUmbenennen(fertigOrdner, serienTitel, staffel, episoden))
|
||||
hinweise.push(...episodenUmbenennen(fertigOrdner, serienTitel, staffel, episoden, undefined, folgenDateien))
|
||||
folgenVergeben = episoden
|
||||
} else {
|
||||
hinweise.push(
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// - Abbruch bei JEDER Zeile prüfen, nicht nur bei Prozent-Änderung —
|
||||
// sonst sieht „Abbrechen" bei einem 4K-Encode minutenlang tot aus.
|
||||
import { werkzeugSpawn } from '../werkzeuge/aufruf'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { existsSync, renameSync, unlinkSync } from 'node:fs'
|
||||
// win32-Pfadregeln ausdrücklich — die CI prüft diese Logik auf Linux.
|
||||
import { win32 as pfad } from 'node:path'
|
||||
import { textVon } from '../rip/parser'
|
||||
@@ -264,14 +264,55 @@ export interface HbLaufOptionen {
|
||||
untertitelStandard?: boolean
|
||||
/** Zusätzliche Schalter ANS ENDE (z. B. --stop-at duration:5 im Messlauf). */
|
||||
extraArgs?: readonly string[]
|
||||
/** Die fertige Datei darf eine vorhandene gleichen Namens ersetzen —
|
||||
* nur beim Wiederholen (auftrag.ersetzt); sonst ist das ein Fehler. */
|
||||
ueberschreiben?: boolean
|
||||
/** Fortschritt 0–100 und Restzeit in Sekunden (-1 = noch keine Angabe). */
|
||||
aufFortschritt?: (prozent: number, restS: number) => void
|
||||
abbruch?: AbortSignal
|
||||
}
|
||||
|
||||
/** Der Zwischenname, unter dem HandBrake schreibt (5.7.0, Durchsicht F9):
|
||||
* `Akira (1988).mkv` → `Akira (1988).teil.mkv`. Die Endung bleibt, damit
|
||||
* --format und die Datei-daneben-Prüfung weiter an der Endung hängen;
|
||||
* erst eine FERTIGE Datei bekommt den echten Namen. Vorher blieb nach
|
||||
* Abbruch oder Fehler eine halbe Datei unter dem endgültigen Namen liegen
|
||||
* — ohne Arbeitsordner direkt in der Ablage, wo Jellyfin sie als kaputten
|
||||
* Film zeigte. */
|
||||
export function zwischenname(ausgabe: string): string {
|
||||
const endung = extname(ausgabe)
|
||||
return endung.length > 0 ? ausgabe.slice(0, -endung.length) + '.teil' + endung : ausgabe + '.teil'
|
||||
}
|
||||
|
||||
/** `x.teil.mkv` → `x.mkv` (auch für die Datei daneben: `x.teil.mp4` → `x.mp4`). */
|
||||
export function endname(zwischen: string): string {
|
||||
return zwischen.replace(/\.teil(\.[^.\\/]+)$/, '$1')
|
||||
}
|
||||
|
||||
/** Reste eines abgebrochenen oder gescheiterten Laufs entfernen — die
|
||||
* Zwischen-Datei und, falls HandBrake einen anderen Container wählte,
|
||||
* ihre Datei daneben. Liefert, was weg ist. */
|
||||
export function zwischenReste(zwischen: string, entfernen: (p: string) => void = unlinkSync, existiert: (p: string) => boolean = existsSync): string[] {
|
||||
const weg: string[] = []
|
||||
for (const kandidat of [zwischen, dateiDaneben(zwischen, existiert)]) {
|
||||
if (kandidat.length === 0 || !existiert(kandidat)) continue
|
||||
try {
|
||||
entfernen(kandidat)
|
||||
weg.push(kandidat)
|
||||
} catch {
|
||||
// Eine Datei, die sich nicht löschen lässt (noch offen?), bleibt —
|
||||
// sie heißt „.teil" und ist damit als halb erkennbar.
|
||||
}
|
||||
}
|
||||
return weg
|
||||
}
|
||||
|
||||
export function komprimieren(optionen: HbLaufOptionen): Promise<HbErgebnis> {
|
||||
return new Promise((fertig) => {
|
||||
const args = buildHandbrakeArgs(optionen)
|
||||
// HandBrake schreibt auf den Zwischennamen; der echte Name kommt erst
|
||||
// mit dem Erfolg (F9).
|
||||
const zwischen = zwischenname(optionen.ausgabe)
|
||||
const args = buildHandbrakeArgs({ ...optionen, ausgabe: zwischen })
|
||||
args.push(...(optionen.extraArgs ?? []))
|
||||
const kind = werkzeugSpawn(optionen.programm, args, { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
|
||||
|
||||
@@ -294,6 +335,7 @@ export function komprimieren(optionen: HbLaufOptionen): Promise<HbErgebnis> {
|
||||
|
||||
kind.on('error', (fehler) => {
|
||||
optionen.abbruch?.removeEventListener('abort', abbrechen)
|
||||
zwischenReste(zwischen)
|
||||
fertig({ status: 'error', fehler: String(fehler) })
|
||||
})
|
||||
|
||||
@@ -301,10 +343,27 @@ export function komprimieren(optionen: HbLaufOptionen): Promise<HbErgebnis> {
|
||||
optionen.abbruch?.removeEventListener('abort', abbrechen)
|
||||
leser.schluss().forEach(zeile)
|
||||
if (abgebrochen) {
|
||||
zwischenReste(zwischen)
|
||||
fertig({ status: 'cancelled' })
|
||||
return
|
||||
}
|
||||
fertig(auswertung.urteil(code, optionen.ausgabe))
|
||||
const urteil = auswertung.urteil(code, zwischen)
|
||||
if (urteil.status !== 'success') {
|
||||
zwischenReste(zwischen)
|
||||
fertig(urteil)
|
||||
return
|
||||
}
|
||||
// Fertig: Zwischenname → echter Name (eine vorhandene Datei gleichen
|
||||
// Namens wäre ein Fehler in der Ablage-Logik — nie still überschreiben).
|
||||
const ziel = endname(urteil.ausgabe)
|
||||
try {
|
||||
if (optionen.ueberschreiben !== true && existsSync(ziel)) throw new Error(`${basename(ziel)} existiert schon`)
|
||||
renameSync(urteil.ausgabe, ziel)
|
||||
} catch (fehler) {
|
||||
fertig({ status: 'error', fehler: `Die fertige Datei ließ sich nicht umbenennen (${String(fehler)}) — sie liegt unter ${urteil.ausgabe}` })
|
||||
return
|
||||
}
|
||||
fertig({ ...urteil, ausgabe: ziel })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -172,3 +172,28 @@ describe('Encoder- und Preset-Listen', () => {
|
||||
expect(parsePresetListe(liste)).toEqual(['Very Fast 2160p60 4K AV1', 'H.265 VCN 2160p 4K'])
|
||||
})
|
||||
})
|
||||
|
||||
// 5.7.0 (Durchsicht F3): Nur die Dateien DIESER Disc werden benannt — der
|
||||
// Season-Ordner darf voll sein.
|
||||
describe('episodenUmbenennen mit nurDiese (5.7.0)', () => {
|
||||
it('Bestand im Ordner stört nicht', () => {
|
||||
const umbenannt: Array<[string, string]> = []
|
||||
const meldungen = episodenUmbenennen(
|
||||
'S',
|
||||
'Akte X',
|
||||
1,
|
||||
[3, 4],
|
||||
{
|
||||
listen: () => ['Akte X S01E01.mkv', 'Akte X S01E02.mkv', 'title_t00.mkv', 'title_t01.mkv'],
|
||||
existiert: () => false,
|
||||
umbenennen: (von, nach) => umbenannt.push([von, nach]),
|
||||
},
|
||||
['title_t00.mkv', 'title_t01.mkv'],
|
||||
)
|
||||
expect(umbenannt).toEqual([
|
||||
[join('S', 'title_t00.mkv'), join('S', 'Akte X S01E03.mkv')],
|
||||
[join('S', 'title_t01.mkv'), join('S', 'Akte X S01E04.mkv')],
|
||||
])
|
||||
expect(meldungen).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -347,3 +347,51 @@ describe('Wiederholen ersetzt die alte Fassung', () => {
|
||||
expect(readFileSync(alt, 'utf8')).toBe('alt')
|
||||
})
|
||||
})
|
||||
|
||||
// ── 5.7.0: Funde der Durchsicht vom 12.09.2026 ─────────────────────────
|
||||
describe('komprimierenUndAblegen — Durchsicht 5.7.0', () => {
|
||||
// F3: Disc 2 derselben Staffel landet im selben Season-Ordner wie Disc 1.
|
||||
// Vorher zählte die Umbenennung ALLE MKVs des Ordners („4 Dateien, 2
|
||||
// Zuordnungen") und tat nichts — der Serien-Merker lief ins Leere.
|
||||
it('F3: zweite Disc in denselben Season-Ordner — nur ihre Folgen werden benannt', async () => {
|
||||
const { ablage, roh, dateien } = aufbau(['title_t00.mkv', 'title_t01.mkv'], ['folge', 'folge'])
|
||||
const season = join(ablage, 'Serien', 'Spartacus', 'Season 01')
|
||||
mkdirSync(season, { recursive: true })
|
||||
writeFileSync(join(season, 'Spartacus S01E01.mkv'), 'disc1')
|
||||
writeFileSync(join(season, 'Spartacus S01E02.mkv'), 'disc1')
|
||||
const ergebnis = await komprimierenUndAblegen(
|
||||
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('tv', 'Spartacus', 2010), staffel: 1, ersteFolge: 3 }),
|
||||
umgebung(ablage),
|
||||
() => {},
|
||||
KEIN_ABBRUCH,
|
||||
)
|
||||
expect(ergebnis.status).toBe('fertig')
|
||||
expect(readdirSync(season).filter((n) => n.endsWith('.mkv')).sort()).toEqual([
|
||||
'Spartacus S01E01.mkv',
|
||||
'Spartacus S01E02.mkv',
|
||||
'Spartacus S01E03.mkv',
|
||||
'Spartacus S01E04.mkv',
|
||||
])
|
||||
expect(readFileSync(join(season, 'Spartacus S01E01.mkv'), 'utf8')).toBe('disc1')
|
||||
expect(ergebnis.folgenVergeben).toEqual([3, 4])
|
||||
})
|
||||
|
||||
// F3: Liegt im Season-Ordner noch ein title_t00.mkv der ersten Disc
|
||||
// (deren Umbenennung ausfiel), darf die zweite es NICHT überschreiben.
|
||||
it('F3: gleichnamige Datei im Ziel wird nie überschrieben — „(2)" statt Verlust', async () => {
|
||||
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'], ['folge'])
|
||||
const season = join(ablage, 'Serien', 'Spartacus', 'Season 01')
|
||||
mkdirSync(season, { recursive: true })
|
||||
writeFileSync(join(season, 'title_t00.mkv'), 'disc1-unbenannt')
|
||||
const ergebnis = await komprimierenUndAblegen(
|
||||
auftrag({ rohOrdner: roh, dateien, zuordnung: zuordnung('tv', 'Spartacus', 2010), staffel: 1 }),
|
||||
umgebung(ablage),
|
||||
() => {},
|
||||
KEIN_ABBRUCH,
|
||||
)
|
||||
expect(ergebnis.status).toBe('fertig')
|
||||
expect(readFileSync(join(season, 'title_t00.mkv'), 'utf8')).toBe('disc1-unbenannt')
|
||||
expect(existsSync(join(season, 'title_t00 (2).mkv'))).toBe(true)
|
||||
expect(ergebnis.dateien.map((d) => d.split(/[\\/]/).pop())).toEqual(['title_t00 (2).mkv'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,3 +40,17 @@ describe('rollenNummern', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
// 5.7.0 (Durchsicht F3): nie überschreiben — ein freier Name, wo einer
|
||||
// nötig ist.
|
||||
describe('freierName', () => {
|
||||
it('frei bleibt frei, belegt bekommt (2), (3) …', async () => {
|
||||
const { freierName } = await import('../src/kern/ablage/dateinamen')
|
||||
const belegt = new Set(['E:\\S\\x.mkv', 'E:\\S\\x (2).mkv'])
|
||||
expect(freierName('E:\\S\\y.mkv', (p) => belegt.has(p))).toBe('E:\\S\\y.mkv')
|
||||
expect(freierName('E:\\S\\x.mkv', (p) => belegt.has(p))).toBe('E:\\S\\x (3).mkv')
|
||||
expect(freierName('/a/b/x.mkv', () => true).startsWith('/a/b/x (')).toBe(true)
|
||||
// Ein Punkt im Ordnernamen ist keine Endung.
|
||||
expect(freierName('E:\\Akira (1988)\\ohne-endung', (p) => p === 'E:\\Akira (1988)\\ohne-endung')).toBe('E:\\Akira (1988)\\ohne-endung (2)')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -220,3 +220,22 @@ describe('etaAusZeile — Restzeit aus der Encoding-Zeile (5.6.0, Format aus dem
|
||||
expect(a.verarbeite('Encoding: task 1 of 1, 11.00 %')).toEqual({ prozent: 11 })
|
||||
})
|
||||
})
|
||||
|
||||
// 5.7.0 (Durchsicht F9): HandBrake schreibt auf einen Zwischennamen; eine
|
||||
// halbe Datei trägt nie den echten Namen.
|
||||
describe('Zwischenname (5.7.0)', () => {
|
||||
it('zwischenname und endname gehören zusammen', async () => {
|
||||
const { zwischenname, endname } = await import('../src/kern/komprimieren/handbrake')
|
||||
expect(zwischenname('E:\\Filme\\Akira (1988)\\Akira (1988).mkv')).toBe('E:\\Filme\\Akira (1988)\\Akira (1988).teil.mkv')
|
||||
expect(endname('E:\\Filme\\Akira (1988)\\Akira (1988).teil.mkv')).toBe('E:\\Filme\\Akira (1988)\\Akira (1988).mkv')
|
||||
expect(endname('E:\\x\\title_t00.teil.mp4')).toBe('E:\\x\\title_t00.mp4')
|
||||
expect(zwischenname('ohne-endung')).toBe('ohne-endung.teil')
|
||||
})
|
||||
it('zwischenReste räumt die Zwischen-Datei und ihre Datei daneben', async () => {
|
||||
const { zwischenReste } = await import('../src/kern/komprimieren/handbrake')
|
||||
const da = new Set(['E:\\x\\a.teil.mkv', 'E:\\x\\a.teil.mp4'])
|
||||
const weg: string[] = []
|
||||
expect(zwischenReste('E:\\x\\a.teil.mkv', (p) => weg.push(p), (p) => da.has(p))).toEqual(['E:\\x\\a.teil.mkv', 'E:\\x\\a.teil.mp4'])
|
||||
expect(zwischenReste('E:\\x\\b.teil.mkv', (p) => weg.push(p), (p) => da.has(p))).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user