real-debrid-downloader/src/renderer/package-order.ts
Sucukdeluxe e212ccc86f Add daily traffic limits, auto-sort packages, Debrid-Link multi-key improvements
Daily traffic limits:
- Per-provider daily download limit (configurable in GB per provider)
- Per Debrid-Link API key daily limit (individual limits per key)
- Usage tracking with automatic daily reset at midnight
- Provider is skipped when daily limit reached, falls back to next provider
- Reset button per provider and per Debrid-Link key in account settings
- Hoster routing skips daily-limited providers gracefully

Debrid-Link multi-key improvements:
- Keys now display with labels (#1, #2...) and masked tokens in account list
- Option to show detailed per-key view with individual usage stats
- Keys that hit their daily limit are automatically skipped
- providerAccountId/providerAccountLabel stored per download item

Auto-sort packages by progress:
- Active packages automatically sorted to top during downloads
- Sorted by completion ratio, then downloaded bytes
- Toggle in settings (autoSortPackagesByProgress)

UI polish:
- Package column headers: flatter, more transparent design
- LinkSnappy mode label: "Login" renamed to "Web"
- Account list: new toggle for detailed Debrid-Link key display
- Account usage stats section with warning styling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:29:48 +01:00

74 lines
2.6 KiB
TypeScript

import type { DownloadItem, DownloadStatus, PackageEntry } from "../shared/types";
const ACTIVE_PACKAGE_STATUSES = new Set<DownloadStatus>(["downloading", "validating", "integrity_check", "extracting"]);
export function reorderPackageOrderByDrop(order: string[], draggedPackageId: string, targetPackageId: string): string[] {
const fromIndex = order.indexOf(draggedPackageId);
const toIndex = order.indexOf(targetPackageId);
if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) {
return order;
}
const next = [...order];
const [dragged] = next.splice(fromIndex, 1);
const insertIndex = Math.max(0, Math.min(next.length, toIndex));
next.splice(insertIndex, 0, dragged);
return next;
}
export function sortPackageOrderByName(order: string[], packages: Record<string, PackageEntry>, descending: boolean): string[] {
const sorted = [...order];
sorted.sort((a, b) => {
const nameA = (packages[a]?.name ?? "").toLowerCase();
const nameB = (packages[b]?.name ?? "").toLowerCase();
const cmp = nameA.localeCompare(nameB, undefined, { numeric: true, sensitivity: "base" });
return descending ? -cmp : cmp;
});
return sorted;
}
export function sortPackagesForDisplay(
packages: PackageEntry[],
itemsById: Record<string, DownloadItem>,
running: boolean,
autoSortPackagesByProgress: boolean
): PackageEntry[] {
if (!running || !autoSortPackagesByProgress || packages.length <= 1) {
return packages;
}
const active: Array<{ pkg: PackageEntry; index: number; completedRatio: number; downloadedBytes: number }> = [];
const rest: PackageEntry[] = [];
packages.forEach((pkg, index) => {
const items = pkg.itemIds
.map((id) => itemsById[id])
.filter((item): item is DownloadItem => Boolean(item));
const hasActive = items.some((item) => ACTIVE_PACKAGE_STATUSES.has(item.status));
if (!hasActive) {
rest.push(pkg);
return;
}
const completedRatio = items.length > 0
? items.filter((item) => item.status === "completed").length / items.length
: 0;
const downloadedBytes = items.reduce((sum, item) => sum + (item.downloadedBytes || 0), 0);
active.push({ pkg, index, completedRatio, downloadedBytes });
});
if (active.length === 0 || active.length === packages.length) {
return packages;
}
active.sort((a, b) => {
if (a.completedRatio !== b.completedRatio) {
return b.completedRatio - a.completedRatio;
}
if (a.downloadedBytes !== b.downloadedBytes) {
return b.downloadedBytes - a.downloadedBytes;
}
return a.index - b.index;
});
return [...active.map((entry) => entry.pkg), ...rest];
}