fix(collector): restore direct DLC drag imports
Resolve dropped file paths through Electron webUtils, send dragged DLC containers directly to Downloads, and keep explicit collector imports bounded so metadata enrichment cannot leave the interface permanently busy.
This commit is contained in:
@@ -14,6 +14,7 @@ interface CollectorInspectionDependencies {
|
||||
checkRapidgator?: typeof checkRapidgatorOnline;
|
||||
resolveFilenames?: (links: string[]) => Promise<Map<string, string>>;
|
||||
createId?: (prefix: "package" | "link") => string;
|
||||
inspectionTimeoutMs?: number;
|
||||
}
|
||||
|
||||
interface SourceLink {
|
||||
@@ -64,6 +65,17 @@ async function runWithConcurrency<T>(items: T[], limit: number, worker: (item: T
|
||||
await Promise.all(runners);
|
||||
}
|
||||
|
||||
async function waitForCollectorMetadata(tasks: Promise<unknown>[], timeoutMs: number): Promise<void> {
|
||||
const all = Promise.all(tasks).then(() => undefined);
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
const timeout = new Promise<void>((resolve) => {
|
||||
timer = setTimeout(resolve, Math.max(1, timeoutMs));
|
||||
timer.unref?.();
|
||||
});
|
||||
await Promise.race([all, timeout]);
|
||||
if (timer) clearTimeout(timer);
|
||||
}
|
||||
|
||||
function countInputLines(rawText: string): { invalidCount: number; duplicateCount: number } {
|
||||
const seen = new Set<string>();
|
||||
let invalidCount = 0;
|
||||
@@ -172,7 +184,11 @@ export async function inspectCollectorPackages(
|
||||
const checkRapidgator = dependencies.checkRapidgator ?? checkRapidgatorOnline;
|
||||
const resolveFilenames = dependencies.resolveFilenames ?? ((urls) => new DebridService(settings).resolveFilenames(urls));
|
||||
|
||||
const oneFichierPromise = checkOneFichier(oneFichierLinks).catch(() => new Map<string, OneFichierCheckResult>());
|
||||
let oneFichierResults = new Map<string, OneFichierCheckResult>();
|
||||
let genericResults = new Map<string, string>();
|
||||
const oneFichierPromise = checkOneFichier(oneFichierLinks)
|
||||
.then((results) => { oneFichierResults = results; })
|
||||
.catch(() => undefined);
|
||||
const rapidgatorPromise = runWithConcurrency(rapidgatorLinks, 8, async (url) => {
|
||||
const result = await checkRapidgator(url).catch(() => null);
|
||||
const link = linksByUrl.get(url);
|
||||
@@ -192,10 +208,13 @@ export async function inspectCollectorPackages(
|
||||
if (result.fileSizeBytes !== null && result.fileSizeBytes >= 0) link.fileSizeBytes = result.fileSizeBytes;
|
||||
});
|
||||
const genericPromise = genericLinks.length > 0
|
||||
? resolveFilenames(genericLinks).catch(() => new Map<string, string>())
|
||||
: Promise.resolve(new Map<string, string>());
|
||||
? resolveFilenames(genericLinks).then((results) => { genericResults = results; }).catch(() => undefined)
|
||||
: Promise.resolve();
|
||||
|
||||
const [oneFichierResults, genericResults] = await Promise.all([oneFichierPromise, genericPromise, rapidgatorPromise, ddownloadPromise]).then(([one, generic]) => [one, generic] as const);
|
||||
await waitForCollectorMetadata(
|
||||
[oneFichierPromise, genericPromise, rapidgatorPromise, ddownloadPromise],
|
||||
dependencies.inspectionTimeoutMs ?? 30_000
|
||||
);
|
||||
for (const [url, result] of oneFichierResults) {
|
||||
const link = linksByUrl.get(url);
|
||||
if (!link) continue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
import { contextBridge, ipcRenderer, webUtils } from "electron";
|
||||
import {
|
||||
AddLinksPayload,
|
||||
AccountCheckScope,
|
||||
@@ -56,7 +56,8 @@ const api: ElectronApi = {
|
||||
ipcRenderer.invoke(IPC_CHANNELS.INSPECT_COLLECTOR_TEXT, request),
|
||||
inspectCollectorContainers: (filePaths: string[], addedAt: number): Promise<CollectorInspectionResult> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.INSPECT_COLLECTOR_CONTAINERS, filePaths, addedAt),
|
||||
getStartConflicts: (): Promise<StartConflictEntry[]> => ipcRenderer.invoke(IPC_CHANNELS.GET_START_CONFLICTS),
|
||||
getPathForDroppedFile: (file: File): string => webUtils.getPathForFile(file),
|
||||
getStartConflicts: (): Promise<StartConflictEntry[]> => ipcRenderer.invoke(IPC_CHANNELS.GET_START_CONFLICTS),
|
||||
resolveStartConflict: (packageId: string, policy: DuplicatePolicy): Promise<StartConflictResolutionResult> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.RESOLVE_START_CONFLICT, packageId, policy),
|
||||
clearAll: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.CLEAR_ALL),
|
||||
|
||||
+13
-3
@@ -49,6 +49,7 @@ import type { AccountEditState, AccountEditTarget, AccountKind, AccountService,
|
||||
import { ACCOUNT_SERVICE_ICONS } from "./account-service-icons";
|
||||
import { DOWNLOAD_SPEED_MAX_SAMPLES, updateDownloadSpeedHistory } from "./download-speed-state";
|
||||
import { createUiLocalizer, normalizeLanguage } from "./i18n";
|
||||
import { importDroppedDlcFiles } from "./collector-drop";
|
||||
import { runLocalBackupExport, runLocalBackupImport, type BackupPassphraseMode } from "./backup-flow";
|
||||
import type { DownloadSpeedHistoryState } from "./download-speed-state";
|
||||
import { extractHoster, formatDateTime, formatSpeedMbps, humanSize, providerLabels } from "./download-format";
|
||||
@@ -3649,11 +3650,20 @@ export function App(): ReactElement {
|
||||
const hasUri = event.dataTransfer.types.includes("text/uri-list");
|
||||
if (!hasFiles && !hasUri) { return; }
|
||||
const files = Array.from(event.dataTransfer.files ?? []) as File[];
|
||||
const dlc = files.filter((f) => f.name.toLowerCase().endsWith(".dlc")).map((f) => (f as unknown as { path?: string }).path).filter((v): v is string => !!v);
|
||||
const dlcFiles = files.filter((file) => file.name.toLowerCase().endsWith(".dlc"));
|
||||
const importFiles = files.filter((f) => /\.(json|txt)$/i.test(f.name));
|
||||
const droppedText = event.dataTransfer.getData("text/plain") || event.dataTransfer.getData("text/uri-list") || "";
|
||||
if (dlc.length > 0) {
|
||||
await enqueueCollectorInspection(() => window.rd.inspectCollectorContainers(dlc, Date.now()));
|
||||
if (dlcFiles.length > 0) {
|
||||
await performQuickAction(async () => {
|
||||
const existingIds = new Set(Object.keys(snapshotRef.current.session.packages));
|
||||
const result = await importDroppedDlcFiles(dlcFiles, window.rd.getPathForDroppedFile, window.rd.addContainers);
|
||||
if (!result || result.addedLinks === 0) throw new Error("Keine gültigen Links in der DLC-Datei gefunden");
|
||||
setTab("downloads");
|
||||
showToast(`Importiert: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`);
|
||||
if (snapshotRef.current.settings.collapseNewPackages) await collapseNewPackages(existingIds);
|
||||
}, (error) => {
|
||||
showToast(`DLC-Import fehlgeschlagen: ${String(error)}`, 3000);
|
||||
});
|
||||
} else if (importFiles.length > 0) {
|
||||
const { queueJson, linkText } = await readCollectorImportFiles(importFiles);
|
||||
await importQueueJsonTexts(queueJson);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
export function resolveDroppedDlcPaths(
|
||||
files: ReadonlyArray<File>,
|
||||
getPathForFile: (file: File) => string
|
||||
): string[] {
|
||||
const paths: string[] = [];
|
||||
for (const file of files) {
|
||||
if (!file.name.toLowerCase().endsWith(".dlc")) continue;
|
||||
try {
|
||||
const filePath = String(getPathForFile(file) || "").trim();
|
||||
if (filePath) paths.push(filePath);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
export async function importDroppedDlcFiles<T>(
|
||||
files: ReadonlyArray<File>,
|
||||
getPathForFile: (file: File) => string,
|
||||
addContainers: (filePaths: string[]) => Promise<T>
|
||||
): Promise<T | null> {
|
||||
const filePaths = resolveDroppedDlcPaths(files, getPathForFile);
|
||||
return filePaths.length > 0 ? addContainers(filePaths) : null;
|
||||
}
|
||||
@@ -82,7 +82,8 @@ export interface ElectronApi {
|
||||
addContainers: (filePaths: string[]) => Promise<{ addedPackages: number; addedLinks: number }>;
|
||||
inspectCollectorText: (request: CollectorInspectionRequest) => Promise<CollectorInspectionResult>;
|
||||
inspectCollectorContainers: (filePaths: string[], addedAt: number) => Promise<CollectorInspectionResult>;
|
||||
getStartConflicts: () => Promise<StartConflictEntry[]>;
|
||||
getPathForDroppedFile: (file: File) => string;
|
||||
getStartConflicts: () => Promise<StartConflictEntry[]>;
|
||||
resolveStartConflict: (packageId: string, policy: DuplicatePolicy) => Promise<StartConflictResolutionResult>;
|
||||
clearAll: () => Promise<void>;
|
||||
start: () => Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user