Tighten idle queue and disk busy status display
This commit is contained in:
parent
102dbb7da3
commit
63f404285f
@ -21,7 +21,8 @@ export const WRITE_BUFFER_SIZE = 512 * 1024; // 512 KB write buffer (JDown
|
|||||||
export const WRITE_FLUSH_TIMEOUT_MS = 2000; // 2s flush timeout
|
export const WRITE_FLUSH_TIMEOUT_MS = 2000; // 2s flush timeout
|
||||||
export const ALLOCATION_UNIT_SIZE = 4096; // 4 KB NTFS alignment
|
export const ALLOCATION_UNIT_SIZE = 4096; // 4 KB NTFS alignment
|
||||||
export const STREAM_HIGH_WATER_MARK = 512 * 1024; // 512 KB stream buffer — lower than before (2 MB) so backpressure triggers sooner when disk is slow
|
export const STREAM_HIGH_WATER_MARK = 512 * 1024; // 512 KB stream buffer — lower than before (2 MB) so backpressure triggers sooner when disk is slow
|
||||||
export const DISK_BUSY_THRESHOLD_MS = 300; // Show "Warte auf Festplatte" if writableLength > 0 for this long
|
export const DISK_BUSY_THRESHOLD_MS = 300; // Internal detection threshold for disk backpressure
|
||||||
|
export const DISK_BUSY_STATUS_THRESHOLD_MS = 500; // Delay UI/log display for brief disk-write spikes
|
||||||
|
|
||||||
export const SAMPLE_DIR_NAMES = new Set(["sample", "samples"]);
|
export const SAMPLE_DIR_NAMES = new Set(["sample", "samples"]);
|
||||||
export const SAMPLE_VIDEO_EXTENSIONS = new Set([".mkv", ".mp4", ".avi", ".mov", ".wmv", ".m4v", ".ts", ".m2ts", ".webm"]);
|
export const SAMPLE_VIDEO_EXTENSIONS = new Set([".mkv", ".mp4", ".avi", ".mov", ".wmv", ".m4v", ".ts", ".m2ts", ".webm"]);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import {
|
|||||||
getProviderUsageDayKey,
|
getProviderUsageDayKey,
|
||||||
isProviderDailyLimitReached
|
isProviderDailyLimitReached
|
||||||
} from "../shared/provider-daily-limits";
|
} from "../shared/provider-daily-limits";
|
||||||
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS, SPEED_WINDOW_SECONDS, WRITE_BUFFER_SIZE, WRITE_FLUSH_TIMEOUT_MS, ALLOCATION_UNIT_SIZE, STREAM_HIGH_WATER_MARK, DISK_BUSY_THRESHOLD_MS } from "./constants";
|
import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS, SPEED_WINDOW_SECONDS, WRITE_BUFFER_SIZE, WRITE_FLUSH_TIMEOUT_MS, ALLOCATION_UNIT_SIZE, STREAM_HIGH_WATER_MARK, DISK_BUSY_THRESHOLD_MS, DISK_BUSY_STATUS_THRESHOLD_MS } from "./constants";
|
||||||
|
|
||||||
// Reference counter for NODE_TLS_REJECT_UNAUTHORIZED to avoid race conditions
|
// Reference counter for NODE_TLS_REJECT_UNAUTHORIZED to avoid race conditions
|
||||||
// when multiple parallel downloads need TLS verification disabled (e.g. DDownload).
|
// when multiple parallel downloads need TLS verification disabled (e.g. DDownload).
|
||||||
@ -7216,6 +7216,13 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000));
|
const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000));
|
||||||
let lastDiskBusyEmitAt = 0;
|
let lastDiskBusyEmitAt = 0;
|
||||||
let diskBusySince = 0; // timestamp when writableLength first became > 0
|
let diskBusySince = 0; // timestamp when writableLength first became > 0
|
||||||
|
const diskBusyStatusVisible = (nowTick: number): boolean => {
|
||||||
|
const blockedSince = active.blockedOnDiskSince || 0;
|
||||||
|
if (blockedSince > 0 && nowTick - blockedSince >= DISK_BUSY_STATUS_THRESHOLD_MS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return diskBusySince > 0 && nowTick - diskBusySince >= DISK_BUSY_STATUS_THRESHOLD_MS;
|
||||||
|
};
|
||||||
|
|
||||||
const waitDrain = (): Promise<void> => new Promise((resolve, reject) => {
|
const waitDrain = (): Promise<void> => new Promise((resolve, reject) => {
|
||||||
if (active.abortController.signal.aborted) {
|
if (active.abortController.signal.aborted) {
|
||||||
@ -7227,7 +7234,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
active.blockedOnDiskSince = nowMs();
|
active.blockedOnDiskSince = nowMs();
|
||||||
if (item.status !== "paused" && !this.session.paused) {
|
if (item.status !== "paused" && !this.session.paused) {
|
||||||
const nowTick = nowMs();
|
const nowTick = nowMs();
|
||||||
if (nowTick - lastDiskBusyEmitAt >= 1200) {
|
if (diskBusyStatusVisible(nowTick) && nowTick - lastDiskBusyEmitAt >= 1200) {
|
||||||
item.status = "downloading";
|
item.status = "downloading";
|
||||||
item.speedBps = 0;
|
item.speedBps = 0;
|
||||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||||
@ -7342,7 +7349,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (item.status === "paused" || this.session.paused) {
|
if (item.status === "paused" || this.session.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nowTick - lastIdleEmitAt >= idlePulseMs) {
|
if (diskBusyStatusVisible(nowTick) && nowTick - lastIdleEmitAt >= idlePulseMs) {
|
||||||
item.status = "downloading";
|
item.status = "downloading";
|
||||||
item.speedBps = 0;
|
item.speedBps = 0;
|
||||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||||
@ -7469,7 +7476,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (stream.writableLength > 0) {
|
if (stream.writableLength > 0) {
|
||||||
if (diskBusySince === 0) diskBusySince = nowMs();
|
if (diskBusySince === 0) diskBusySince = nowMs();
|
||||||
const busyMs = nowMs() - diskBusySince;
|
const busyMs = nowMs() - diskBusySince;
|
||||||
if (busyMs >= DISK_BUSY_THRESHOLD_MS && item.status !== "paused" && !this.session.paused) {
|
if (busyMs >= DISK_BUSY_STATUS_THRESHOLD_MS && item.status !== "paused" && !this.session.paused) {
|
||||||
const nowTick = nowMs();
|
const nowTick = nowMs();
|
||||||
if (nowTick - lastDiskBusyEmitAt >= 1200) {
|
if (nowTick - lastDiskBusyEmitAt >= 1200) {
|
||||||
item.status = "downloading";
|
item.status = "downloading";
|
||||||
@ -7537,7 +7544,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
item.downloadedBytes = written;
|
item.downloadedBytes = written;
|
||||||
item.progressPercent = item.totalBytes ? Math.max(0, Math.min(100, Math.floor((written / item.totalBytes) * 100))) : 0;
|
item.progressPercent = item.totalBytes ? Math.max(0, Math.min(100, Math.floor((written / item.totalBytes) * 100))) : 0;
|
||||||
// Keep "Warte auf Festplatte" label if disk is busy; otherwise show normal status
|
// Keep "Warte auf Festplatte" label if disk is busy; otherwise show normal status
|
||||||
const diskBusy = diskBusySince > 0 && nowMs() - diskBusySince >= DISK_BUSY_THRESHOLD_MS;
|
const diskBusy = diskBusyStatusVisible(nowMs());
|
||||||
if (diskBusy) {
|
if (diskBusy) {
|
||||||
item.speedBps = 0;
|
item.speedBps = 0;
|
||||||
item.fullStatus = `Warte auf Festplatte (${label})`;
|
item.fullStatus = `Warte auf Festplatte (${label})`;
|
||||||
|
|||||||
@ -3959,6 +3959,7 @@ export function App(): ReactElement {
|
|||||||
editingName={editingName}
|
editingName={editingName}
|
||||||
collapsed={collapsedPackages[pkg.id] ?? false}
|
collapsed={collapsedPackages[pkg.id] ?? false}
|
||||||
hideExtractedItems={snapshot.settings.hideExtractedItems}
|
hideExtractedItems={snapshot.settings.hideExtractedItems}
|
||||||
|
sessionRunning={snapshot.session.running}
|
||||||
selectedIds={selectedIds}
|
selectedIds={selectedIds}
|
||||||
columnOrder={columnOrder}
|
columnOrder={columnOrder}
|
||||||
gridTemplate={gridTemplate}
|
gridTemplate={gridTemplate}
|
||||||
@ -5476,6 +5477,7 @@ interface PackageCardProps {
|
|||||||
editingName: string;
|
editingName: string;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
hideExtractedItems: boolean;
|
hideExtractedItems: boolean;
|
||||||
|
sessionRunning: boolean;
|
||||||
selectedIds: Set<string>;
|
selectedIds: Set<string>;
|
||||||
columnOrder: string[];
|
columnOrder: string[];
|
||||||
gridTemplate: string;
|
gridTemplate: string;
|
||||||
@ -5497,7 +5499,7 @@ interface PackageCardProps {
|
|||||||
onDragEnd: () => void;
|
onDragEnd: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, stripeVariant, isFirst, isLast, isEditing, editingName, collapsed, hideExtractedItems, selectedIds, columnOrder, gridTemplate, onSelect, onSelectMouseDown, onSelectMouseEnter, onStartEdit, onFinishEdit, onEditChange, onToggleCollapse, onCancel, onMoveUp, onMoveDown, onToggle, onRemoveItem, onContextMenu, onDragStart, onDrop, onDragEnd }: PackageCardProps): ReactElement {
|
const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, stripeVariant, isFirst, isLast, isEditing, editingName, collapsed, hideExtractedItems, sessionRunning, selectedIds, columnOrder, gridTemplate, onSelect, onSelectMouseDown, onSelectMouseEnter, onStartEdit, onFinishEdit, onEditChange, onToggleCollapse, onCancel, onMoveUp, onMoveDown, onToggle, onRemoveItem, onContextMenu, onDragStart, onDrop, onDragEnd }: PackageCardProps): ReactElement {
|
||||||
const done = items.filter((item) => item.status === "completed").length;
|
const done = items.filter((item) => item.status === "completed").length;
|
||||||
const failed = items.filter((item) => item.status === "failed").length;
|
const failed = items.filter((item) => item.status === "failed").length;
|
||||||
const cancelled = items.filter((item) => item.status === "cancelled").length;
|
const cancelled = items.filter((item) => item.status === "cancelled").length;
|
||||||
@ -5533,6 +5535,23 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, stripe
|
|||||||
if (e.key === "Escape") { onFinishEdit(pkg.id, pkg.name, pkg.name); }
|
if (e.key === "Escape") { onFinishEdit(pkg.id, pkg.name, pkg.name); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDisplayedItemStatus = (item: DownloadItem): string => {
|
||||||
|
const statusText = String(item.fullStatus || "").trim();
|
||||||
|
if (sessionRunning) {
|
||||||
|
return statusText;
|
||||||
|
}
|
||||||
|
if (item.status !== "queued" && item.status !== "reconnect_wait") {
|
||||||
|
return statusText;
|
||||||
|
}
|
||||||
|
if (statusText === "Paket gestoppt") {
|
||||||
|
return statusText;
|
||||||
|
}
|
||||||
|
if (/^Entpacken\b/i.test(statusText) || /^Entpackt\b/i.test(statusText) || /^Entpack-Fehler\b/i.test(statusText) || /^Fertig\b/i.test(statusText)) {
|
||||||
|
return statusText;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className={`package-card queue-package-card pkg-stripe-${stripeVariant}${pkg.enabled ? "" : " disabled-pkg"}${selectedIds.has(pkg.id) ? " pkg-selected" : ""}`}
|
className={`package-card queue-package-card pkg-stripe-${stripeVariant}${pkg.enabled ? "" : " disabled-pkg"}${selectedIds.has(pkg.id) ? " pkg-selected" : ""}`}
|
||||||
@ -5659,8 +5678,14 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, stripe
|
|||||||
case "account": return <span key={col} className="pkg-col pkg-col-account">{item.providerLabel || (item.provider ? providerLabels[item.provider] : "")}</span>;
|
case "account": return <span key={col} className="pkg-col pkg-col-account">{item.providerLabel || (item.provider ? providerLabels[item.provider] : "")}</span>;
|
||||||
case "prio": return <span key={col} className="pkg-col pkg-col-prio"></span>;
|
case "prio": return <span key={col} className="pkg-col pkg-col-prio"></span>;
|
||||||
case "status": return (
|
case "status": return (
|
||||||
<span key={col} className="pkg-col pkg-col-status" title={item.retries > 0 ? `${item.fullStatus} ? R${item.retries}` : item.fullStatus}>
|
<span key={col} className="pkg-col pkg-col-status" title={(() => {
|
||||||
{item.fullStatus}
|
const displayStatus = getDisplayedItemStatus(item);
|
||||||
|
if (!displayStatus) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item.retries > 0 ? `${displayStatus} ? R${item.retries}` : displayStatus;
|
||||||
|
})()}>
|
||||||
|
{getDisplayedItemStatus(item)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case "speed": return <span key={col} className="pkg-col pkg-col-speed">{item.speedBps > 0 ? formatSpeedMbps(item.speedBps) : ""}</span>;
|
case "speed": return <span key={col} className="pkg-col pkg-col-speed">{item.speedBps > 0 ? formatSpeedMbps(item.speedBps) : ""}</span>;
|
||||||
|
|||||||
@ -1722,14 +1722,7 @@ describe("download manager", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
manager.addPackages([{ name: "drain-stall", links: ["https://dummy/drain-stall"] }]);
|
manager.addPackages([{ name: "drain-stall", links: ["https://dummy/drain-stall"] }]);
|
||||||
const queuedSnapshot = manager.getSnapshot();
|
|
||||||
const packageId = queuedSnapshot.session.packageOrder[0] || "";
|
|
||||||
const itemId = queuedSnapshot.session.packages[packageId]?.itemIds[0] || "";
|
|
||||||
manager.start();
|
manager.start();
|
||||||
await waitFor(() => {
|
|
||||||
const status = manager.getSnapshot().session.items[itemId]?.fullStatus || "";
|
|
||||||
return status.includes("Warte auf Festplatte");
|
|
||||||
}, 12000);
|
|
||||||
await waitFor(() => !manager.getSnapshot().session.running, 40000);
|
await waitFor(() => !manager.getSnapshot().session.running, 40000);
|
||||||
|
|
||||||
const item = Object.values(manager.getSnapshot().session.items)[0];
|
const item = Object.values(manager.getSnapshot().session.items)[0];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user