release: prepare v2.0.82

This commit is contained in:
Sucukdeluxe
2026-09-01 00:13:30 +02:00
parent d58cfba817
commit e7639bc4df
23 changed files with 313 additions and 59 deletions
+8
View File
@@ -88,6 +88,7 @@ import { normalizeStatisticsLedger, saveStatisticsLedger } from "./statistics-le
import { NotificationOutbox } from "./notification-outbox";
import { sendNotification } from "./notify";
import { DownloadHealthMonitor } from "./download-health-monitor";
import { ensureStartupWorkDirectories } from "./startup-work-directories";
import { shouldDeferAutoResumeToDailyStart } from "./daily-start-scheduler";
import { configureNetworkProxy, getNetworkProxyState, shutdownNetworkProxy } from "./network-proxy";
import { createProxyOnlyAccountError, resolveProxyOnlyAccountErrorCode } from "./proxy-account-errors";
@@ -173,6 +174,13 @@ export class AppController {
}
this.applyNetworkProxyConfiguration();
this.initializeLogStorage();
const workDirectories = ensureStartupWorkDirectories(this.settings);
if (workDirectories.created.length > 0) {
logger.info(`Arbeitsordner beim Start angelegt: ${workDirectories.created.map((entry) => entry.kind).join(", ")}`);
}
for (const failure of workDirectories.failures) {
logger.warn(`Arbeitsordner konnte beim Start nicht angelegt werden (${failure.kind}): ${failure.error}`);
}
this.runHistoryLifecycleCleanup("Start", () => resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode));
const loadResult = loadSessionWithStatus(this.storagePaths);
const session = loadResult.session;
+4 -3
View File
@@ -79,9 +79,10 @@ export function defaultSettings(): AppSettings {
providerPrimary: "realdebrid",
providerSecondary: "megadebrid-api",
providerTertiary: "bestdebrid",
autoProviderFallback: true,
outputDir: baseDir,
packageName: "",
autoProviderFallback: true,
outputDir: baseDir,
createWorkDirectoriesOnStartup: false,
packageName: "",
autoExtract: true,
autoRename4sf4sj: false,
keepGermanAudioOnly: false,
+1
View File
@@ -179,6 +179,7 @@ export function createRendererSettings(settings: AppSettings): RendererSettings
providerTertiary: settings.providerTertiary,
autoProviderFallback: settings.autoProviderFallback,
outputDir: settings.outputDir,
createWorkDirectoriesOnStartup: settings.createWorkDirectoriesOnStartup,
packageName: settings.packageName,
autoExtract: settings.autoExtract,
autoRename4sf4sj: settings.autoRename4sf4sj,
+57
View File
@@ -0,0 +1,57 @@
import fs from "node:fs";
import path from "node:path";
import type { AppSettings } from "../shared/types";
export type StartupWorkDirectoryKind = "download" | "extract" | "video-library";
export interface StartupWorkDirectoryEntry {
kind: StartupWorkDirectoryKind;
directory: string;
}
export interface StartupWorkDirectoryFailure extends StartupWorkDirectoryEntry {
error: string;
}
export interface StartupWorkDirectoryResult {
created: StartupWorkDirectoryEntry[];
existing: StartupWorkDirectoryEntry[];
failures: StartupWorkDirectoryFailure[];
}
function directoryKey(directory: string): string {
const resolved = path.resolve(directory);
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
}
export function ensureStartupWorkDirectories(settings: AppSettings): StartupWorkDirectoryResult {
const result: StartupWorkDirectoryResult = { created: [], existing: [], failures: [] };
if (!settings.createWorkDirectoriesOnStartup) {
return result;
}
const candidates: StartupWorkDirectoryEntry[] = [
{ kind: "download", directory: settings.outputDir },
...(settings.autoExtract ? [{ kind: "extract" as const, directory: settings.extractDir }] : []),
...(settings.collectMkvToLibrary ? [{ kind: "video-library" as const, directory: settings.mkvLibraryDir }] : [])
];
const seen = new Set<string>();
for (const candidate of candidates) {
const entry = { ...candidate, directory: path.resolve(candidate.directory) };
const key = directoryKey(entry.directory);
if (seen.has(key)) {
continue;
}
seen.add(key);
const existed = fs.existsSync(entry.directory);
try {
fs.mkdirSync(entry.directory, { recursive: true });
(existed ? result.existing : result.created).push(entry);
} catch (error) {
result.failures.push({ ...entry, error: String(error) });
}
}
return result;
}
+6 -3
View File
@@ -585,9 +585,12 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
providerPrimary: normalizeConfiguredProvider(settings.providerPrimary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled) || defaults.providerPrimary,
providerSecondary: normalizeFallbackProvider(settings.providerSecondary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled),
providerTertiary: normalizeFallbackProvider(settings.providerTertiary, megaDebridPreferApi, megaDebridApiEnabled, megaDebridWebEnabled),
autoProviderFallback: Boolean(settings.autoProviderFallback),
outputDir: normalizeAbsoluteDir(directorySettings.outputDir, defaults.outputDir),
packageName: asText(settings.packageName),
autoProviderFallback: Boolean(settings.autoProviderFallback),
outputDir: normalizeAbsoluteDir(directorySettings.outputDir, defaults.outputDir),
createWorkDirectoriesOnStartup: settings.createWorkDirectoriesOnStartup !== undefined
? Boolean(settings.createWorkDirectoriesOnStartup)
: defaults.createWorkDirectoriesOnStartup,
packageName: asText(settings.packageName),
autoExtract: Boolean(settings.autoExtract),
autoRename4sf4sj: Boolean(settings.autoRename4sf4sj),
keepGermanAudioOnly: Boolean(settings.keepGermanAudioOnly),
+1 -1
View File
@@ -946,7 +946,7 @@ const emptySnapshot = (): UiSnapshot => ({
debridLinkDisabledKeyIds: [],
archivePasswordListConfigured: false, notifyUrlConfigured: false,
rememberToken: true, configuredProviders: [], providerOrder: [], providerPrimary: "realdebrid", providerSecondary: "none",
providerTertiary: "none", autoProviderFallback: true, outputDir: "", packageName: "",
providerTertiary: "none", autoProviderFallback: true, outputDir: "", createWorkDirectoriesOnStartup: false, packageName: "",
autoExtract: true, autoRename4sf4sj: false, keepGermanAudioOnly: false, germanAudioMode: "tag", extractDir: "", createExtractSubfolder: true, hybridExtract: true,
collectMkvToLibrary: false, mkvLibraryDir: "",
cleanupMode: "none", extractConflictMode: "overwrite", removeLinkFilesAfterExtract: false,
+4 -4
View File
@@ -1,11 +1,11 @@
import type { DebridProvider, RendererSettings, RendererSettingsUpdate } from "../shared/types";
const proxyOnlyAccountMessages = {
proxy_list_missing: "Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit.",
proxy_list_unreadable: "Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste kann nicht gelesen werden. Prüfe die Datei unter Einstellungen → Geschwindigkeit.",
proxy_list_missing: "Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit & Proxy.",
proxy_list_unreadable: "Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste kann nicht gelesen werden. Prüfe die Datei unter Einstellungen → Geschwindigkeit & Proxy.",
proxy_list_empty: "Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste ist leer oder enthält keine gültigen HTTP-Proxys.",
proxy_index_unavailable: "Proxy-only ist aktiviert, aber der feste API-Proxy ist in der Liste nicht verfügbar. Prüfe den Listeneintrag unter Einstellungen → Geschwindigkeit.",
proxy_unreachable: "Proxy-only ist aktiviert, aber der feste API-Proxy ist nicht erreichbar oder lehnt die Verbindung ab. Prüfe den Proxy unter Einstellungen → Geschwindigkeit."
proxy_index_unavailable: "Proxy-only ist aktiviert, aber der feste API-Proxy ist in der Liste nicht verfügbar. Prüfe den Listeneintrag unter Einstellungen → Geschwindigkeit & Proxy.",
proxy_unreachable: "Proxy-only ist aktiviert, aber der feste API-Proxy ist nicht erreichbar oder lehnt die Verbindung ab. Prüfe den Proxy unter Einstellungen → Geschwindigkeit & Proxy."
} as const;
export function formatAccountOperationError(prefix: string, error: unknown): string {
+7 -7
View File
@@ -5,12 +5,12 @@ const pairs = [
["Datei", "File"], ["Hilfe", "Help"], ["Kontomenü", "Account menu"], ["Allgemein", "General"], ["Accounts", "Accounts"],
["Hauptnavigation", "Main navigation"], ["Globale Aktionen", "Global actions"], ["Anwendungsmenü", "Application menu"], ["Seitenleiste einklappen", "Collapse sidebar"], ["Seitenleiste ausklappen", "Expand sidebar"],
["Aktuelle Download-Geschwindigkeit (geglättet)", "Current download speed (smoothed)"], ["Einstellungsbereich", "Settings area"],
["Entpacken", "Extraction"], ["Geschwindigkeit", "Speed"], ["Bereinigung", "Cleanup"], ["Updates", "Updates"],
["Entpacken", "Extraction"], ["Geschwindigkeit", "Speed"], ["Geschwindigkeit & Proxy", "Speed & Proxy"], ["Bereinigung", "Cleanup"], ["Updates", "Updates"],
["Einstellungen speichern", "Save settings"], ["Änderungen verwerfen", "Discard changes"], ["Stellt den letzten gespeicherten Stand wieder her.", "Restores the last saved settings."], ["Ungespeicherte Änderungen verworfen", "Unsaved changes discarded"], ["Zwischenstand gespeichert weitere Änderungen sind ungespeichert", "Progress saved additional changes remain unsaved"], ["Gespeichert", "Saved"], ["Ungespeicherte Änderungen", "Unsaved changes"], ["Wird gespeichert…", "Saving…"], ["Speichern fehlgeschlagen", "Save failed"],
["Sprache", "Language"], ["Speicherort", "Storage location"], ["Download-Verhalten", "Download behavior"], ["Oberfläche und Bedienung", "Interface and controls"], ["Discord-Benachrichtigungen", "Discord notifications"],
["Speicherort, Download-Verhalten, Verlauf, Oberfläche und Benachrichtigungen.", "Storage location, download behavior, history, interface and notifications."], ["Hell", "Light"], ["Dunkel", "Dark"],
["Download-Ordner", "Download folder"], ["Paketname (optional)", "Package name (optional)"], ["Max. gleichzeitige Downloads", "Max. concurrent downloads"], ["Automatische Wiederholungen", "Automatic retries"],
["Zielordner für heruntergeladene Dateien.", "Destination folder for downloaded files."],
["Zielordner für heruntergeladene Dateien.", "Destination folder for downloaded files."], ["Fehlende Arbeitsordner beim Start anlegen", "Create missing work folders on startup"], ["Erstellt den Download-Ordner sowie bei aktiver Funktion den Entpack- und Videosammelordner neu. Vorhandene Ordner und Inhalte bleiben unverändert.", "Creates the download folder and, when enabled, the extraction and video library folders. Existing folders and contents remain unchanged."],
["Beim Start automatisch fortsetzen", "Resume automatically on startup"], ["Zwischenablage überwachen", "Monitor clipboard"], ["Verlauf speichern", "Save history"], ["Nur aktuelle Session", "Current session only"], ["Nur letzte 100 Einträge", "Last 100 entries only"], ["Nur letzte 250 Einträge", "Last 250 entries only"], ["Dauerhaft", "Permanent"],
["Maximale Verlauf-Einträge", "Maximum history entries"], ["Einträge löschen älter als (Tage)", "Delete entries older than (days)"], ["Neue Pakete eingeklappt zeigen", "Show new packages collapsed"], ["Animationen", "Animations"],
["In den Infobereich minimieren", "Minimize to tray"], ["Vor dem Löschen nachfragen", "Confirm before deleting"], ["Download-Liste mitsichern", "Include download list in backup"],
@@ -46,7 +46,7 @@ const pairs = [
["Ablageform", "Output layout"], ["Automatisch umbenennen", "Rename automatically"], ["In Paket-Unterordner ablegen", "Store in package subfolder"], ["Videos in Sammelordner verschieben", "Move videos to library folder"],
["Video-Sammelordner", "Video library folder"], ["Leistung", "Performance"], ["Hybrid-Entpacken", "Hybrid extraction"], ["Gleichzeitige Entpackungen", "Concurrent extractions"], ["CPU-Priorität beim Entpacken", "CPU priority during extraction"],
["Passwörter", "Passwords"], ["Passwortliste für Archive", "Archive password list"], ["Ein Passwort pro Zeile", "One password per line"], ["Hoch (80% CPU)", "High (80% CPU)"], ["Mittel (50% CPU)", "Medium (50% CPU)"], ["Niedrig (25% CPU)", "Low (25% CPU)"],
["Tempo, Wiederverbindung und zeitgesteuerte Bandbreitenregeln.", "Speed, reconnection and scheduled bandwidth rules."], ["Tempo-Begrenzung", "Speed limit"], ["Geschwindigkeit begrenzen", "Limit speed"],
["Tempo, Proxy, Wiederverbindung und zeitgesteuerte Bandbreitenregeln.", "Speed, proxy, reconnection and scheduled bandwidth rules."], ["Tempo-Begrenzung", "Speed limit"], ["Geschwindigkeit begrenzen", "Limit speed"],
["Höchstgeschwindigkeit (MB/s)", "Maximum speed (MB/s)"], ["Limit gilt für", "Limit applies to"], ["Verbindung", "Connection"], ["Automatisch neu verbinden", "Reconnect automatically"], ["Wartezeit vor neuem Versuch (Sek.)", "Wait before retry (sec.)"],
["Von (Stunde)", "From (hour)"], ["Bis (Stunde)", "To (hour)"], ["Limit (MB/s)", "Limit (MB/s)"], ["Zeitregel aktiviert", "Schedule rule enabled"], ["Zeitregel entfernen", "Remove schedule rule"],
["Bandbreitenplanung", "Bandwidth schedule"], ["Jede Regel legt für ein Zeitfenster ein eigenes Limit fest.", "Each rule sets a separate limit for a time window."], ["Weitere Zeitregel", "Additional schedule rule"], ["Zeitregel hinzufügen", "Add schedule rule"],
@@ -197,11 +197,11 @@ const pairs = [
["DDownload Login", "DDownload login"], ["Debrid-Link API", "Debrid-Link API"], ["LinkSnappy Web-Login", "LinkSnappy web login"],
["Tageslimit erreicht. Neue Links wechseln auf den nächsten Hoster.", "Daily limit reached. New links will switch to the next hoster."], ["Mega-Debrid: Bitte Login und Passwort eintragen.", "Mega-Debrid: Enter a login and password."],
["Dieser Mega-Debrid-Account ist bereits vorhanden.", "This Mega-Debrid account already exists."], ["Debrid-Link: Bitte genau einen API-Key eintragen.", "Debrid-Link: Enter exactly one API key."],
["Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but no proxy list is configured. Add one under Settings → Speed."],
["Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste kann nicht gelesen werden. Prüfe die Datei unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but the configured proxy list cannot be read. Check the file under Settings → Speed."],
["Proxy-only ist aktiviert, aber es ist keine Proxy-Liste hinterlegt. Hinterlege sie unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but no proxy list is configured. Add one under Settings → Speed & Proxy."],
["Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste kann nicht gelesen werden. Prüfe die Datei unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but the configured proxy list cannot be read. Check the file under Settings → Speed & Proxy."],
["Proxy-only ist aktiviert, aber die hinterlegte Proxy-Liste ist leer oder enthält keine gültigen HTTP-Proxys.", "Proxy-only is enabled, but the configured proxy list is empty or contains no valid HTTP proxies."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist in der Liste nicht verfügbar. Prüfe den Listeneintrag unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but the fixed API proxy is not available in the list. Check the list entry under Settings → Speed."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist nicht erreichbar oder lehnt die Verbindung ab. Prüfe den Proxy unter Einstellungen → Geschwindigkeit.", "Proxy-only is enabled, but the fixed API proxy is unreachable or refuses the connection. Check the proxy under Settings → Speed."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist in der Liste nicht verfügbar. Prüfe den Listeneintrag unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but the fixed API proxy is not available in the list. Check the list entry under Settings → Speed & Proxy."],
["Proxy-only ist aktiviert, aber der feste API-Proxy ist nicht erreichbar oder lehnt die Verbindung ab. Prüfe den Proxy unter Einstellungen → Geschwindigkeit & Proxy.", "Proxy-only is enabled, but the fixed API proxy is unreachable or refuses the connection. Check the proxy under Settings → Speed & Proxy."],
["Die Prüfung hat nicht genau den neuen Account bestätigt.", "The check did not confirm exactly the new account."], ["Nur lokal gebunden", "Bound locally only"], ["(nur lokal)", "(local only)"], ["Nur lokal", "Local only"],
["Bindet nur an 127.0.0.1. Fernzugriff nur ueber einen Tunnel (z.B. Tailscale/SSH) - die sicherste Variante.", "Binds only to 127.0.0.1. Remote access only through a tunnel (such as Tailscale/SSH) - the safest option."],
["Bindet an 0.0.0.0. Erreichbar im Netzwerk, erfordert eine Allowlist. Nur in vertrauenswuerdigen Netzen/VPN nutzen.", "Binds to 0.0.0.0. Reachable on the network and requires an allowlist. Use only on trusted networks or VPNs."],
@@ -149,7 +149,7 @@ export function getDownloadQueueStatusMetrics(items: readonly DownloadItem[]): {
export function formatRemainingDownloadBytes(summary: { bytes: number; unknownItems: number }): string {
const value = summary.bytes >= 1024 ** 4
? `${(summary.bytes / 1024 ** 4).toFixed(4)} TB`
? `${(summary.bytes / 1024 ** 4).toFixed(5)} TB`
: humanSize(summary.bytes);
if (summary.unknownItems <= 0) return value;
return summary.bytes > 0 ? `${value}` : "Unbekannt";
+10 -3
View File
@@ -10,7 +10,7 @@ export const SETTINGS_SECTIONS: readonly { id: SettingsSection; label: string }[
{ id: "allgemein", label: "Allgemein" },
{ id: "accounts", label: "Accounts" },
{ id: "extract", label: "Entpacken" },
{ id: "speed", label: "Geschwindigkeit" },
{ id: "speed", label: "Geschwindigkeit & Proxy" },
{ id: "cleanup", label: "Bereinigung" },
{ id: "updates", label: "Updates" }
];
@@ -392,8 +392,8 @@ export function buildSettingsFormViewModel({
};
});
return {
title: "Geschwindigkeit",
description: "Tempo, Wiederverbindung und zeitgesteuerte Bandbreitenregeln.",
title: "Geschwindigkeit & Proxy",
description: "Tempo, Proxy, Wiederverbindung und zeitgesteuerte Bandbreitenregeln.",
groups: [
{
id: "speed-limit",
@@ -583,6 +583,13 @@ export function buildSettingsFormViewModel({
title: "Speicherort",
fields: [
{ id: "outputDir", kind: "path", label: "Download-Ordner", value: settings.outputDir, actionLabel: "Wählen", help: "Zielordner für heruntergeladene Dateien." },
{
id: "createWorkDirectoriesOnStartup",
kind: "switch",
label: "Fehlende Arbeitsordner beim Start anlegen",
value: settings.createWorkDirectoriesOnStartup,
help: "Erstellt den Download-Ordner sowie bei aktiver Funktion den Entpack- und Videosammelordner neu. Vorhandene Ordner und Inhalte bleiben unverändert."
},
{ id: "packageName", kind: "text", label: "Paketname (optional)", value: settings.packageName },
{
id: "logStorageLocation",
+6 -4
View File
@@ -181,10 +181,11 @@ export interface AppSettings extends DailyStartSettings, ProxyDownloadSettings {
providerOrder: readonly DebridProvider[];
providerPrimary: DebridProvider;
providerSecondary: DebridFallbackProvider;
providerTertiary: DebridFallbackProvider;
autoProviderFallback: boolean;
outputDir: string;
packageName: string;
providerTertiary: DebridFallbackProvider;
autoProviderFallback: boolean;
outputDir: string;
createWorkDirectoriesOnStartup: boolean;
packageName: string;
autoExtract: boolean;
autoRename4sf4sj: boolean;
keepGermanAudioOnly: boolean;
@@ -319,6 +320,7 @@ export interface RendererSettings extends DailyStartSettings, ProxyDownloadSetti
providerTertiary: DebridFallbackProvider;
autoProviderFallback: boolean;
outputDir: string;
createWorkDirectoriesOnStartup: boolean;
packageName: string;
autoExtract: boolean;
autoRename4sf4sj: boolean;