feat(history): update the open view live
This commit is contained in:
@@ -118,7 +118,9 @@ export class AppController {
|
||||
|
||||
private logDirectory = this.storagePaths.baseDir;
|
||||
|
||||
private onStateHandler: ((snapshot: UiSnapshot) => void) | null = null;
|
||||
private onStateHandler: ((snapshot: UiSnapshot) => void) | null = null;
|
||||
|
||||
private onHistoryEntryAddedHandler: ((entry: HistoryEntry) => void) | null = null;
|
||||
|
||||
private autoResumePending = false;
|
||||
private runtimeStatsTimer: NodeJS.Timeout | null = null;
|
||||
@@ -160,10 +162,10 @@ export class AppController {
|
||||
realDebridWebUnrestrict: (accountId: string, link: string, signal?: AbortSignal) => this.unrestrictRealDebridWebAccount(accountId, link, signal),
|
||||
bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal),
|
||||
invalidateMegaSession: () => this.megaWebFallback.invalidateSession(),
|
||||
protectEmptyClobber: loadResult.status === "empty-unreadable",
|
||||
onHistoryEntry: (entry: HistoryEntry) => {
|
||||
addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry, this.historyLimits());
|
||||
}
|
||||
protectEmptyClobber: loadResult.status === "empty-unreadable",
|
||||
onHistoryEntry: (entry: HistoryEntry) => {
|
||||
this.recordHistoryEntry(entry);
|
||||
}
|
||||
});
|
||||
this.manager.on("state", (snapshot: UiSnapshot) => {
|
||||
this.onStateHandler?.(snapshot);
|
||||
@@ -270,7 +272,7 @@ export class AppController {
|
||||
return this.onStateHandler;
|
||||
}
|
||||
|
||||
public set onState(handler: ((snapshot: UiSnapshot) => void) | null) {
|
||||
public set onState(handler: ((snapshot: UiSnapshot) => void) | null) {
|
||||
this.onStateHandler = handler;
|
||||
if (handler) {
|
||||
handler(this.manager.getSnapshot());
|
||||
@@ -453,6 +455,10 @@ export class AppController {
|
||||
overlayLiveUsageCounters(target, liveSettings, this.manager.getLiveTotalRuntimeMs());
|
||||
}
|
||||
|
||||
public set onHistoryEntryAdded(handler: ((entry: HistoryEntry) => void) | null) {
|
||||
this.onHistoryEntryAddedHandler = handler;
|
||||
}
|
||||
|
||||
private applySettingsOnlyBackup(importedSettings: AppSettings, remoteDiagnostics?: unknown, restoreRemoteDiagnostics = false): void {
|
||||
let restoredSettings = normalizeSettings(importedSettings);
|
||||
if (this.settings.logStorageLocation !== restoredSettings.logStorageLocation
|
||||
@@ -1340,9 +1346,16 @@ export class AppController {
|
||||
return true;
|
||||
}
|
||||
|
||||
private historyLimits(): { maxEntries: number; maxAgeDays: number } {
|
||||
return { maxEntries: this.settings.historyMaxEntries, maxAgeDays: this.settings.historyMaxAgeDays };
|
||||
}
|
||||
private historyLimits(): { maxEntries: number; maxAgeDays: number } {
|
||||
return { maxEntries: this.settings.historyMaxEntries, maxAgeDays: this.settings.historyMaxAgeDays };
|
||||
}
|
||||
|
||||
private recordHistoryEntry(entry: HistoryEntry): void {
|
||||
const entries = addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry, this.historyLimits());
|
||||
if (entries[0]?.id === entry.id) {
|
||||
this.onHistoryEntryAddedHandler?.(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public getHistory(): HistoryEntry[] {
|
||||
return loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode, this.historyLimits());
|
||||
|
||||
+10
-4
@@ -923,13 +923,19 @@ function registerIpcHandlers(): void {
|
||||
}
|
||||
});
|
||||
|
||||
controller.onState = (snapshot) => {
|
||||
controller.onState = (snapshot) => {
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
mainWindow.webContents.send(IPC_CHANNELS.STATE_UPDATE, snapshot);
|
||||
};
|
||||
}
|
||||
mainWindow.webContents.send(IPC_CHANNELS.STATE_UPDATE, snapshot);
|
||||
};
|
||||
controller.onHistoryEntryAdded = (entry) => {
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
mainWindow.webContents.send(IPC_CHANNELS.HISTORY_ENTRY_ADDED, entry);
|
||||
};
|
||||
}
|
||||
|
||||
function formatRendererErrorReport(rawReport: unknown): string {
|
||||
const report = (rawReport && typeof rawReport === "object" ? rawReport : {}) as Record<string, unknown>;
|
||||
|
||||
@@ -115,6 +115,13 @@ const api: ElectronApi = {
|
||||
extractNow: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId),
|
||||
resetPackage: (packageId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.RESET_PACKAGE, packageId),
|
||||
getHistory: (): Promise<HistoryEntry[]> => ipcRenderer.invoke(IPC_CHANNELS.GET_HISTORY),
|
||||
onHistoryEntryAdded: (callback: (entry: HistoryEntry) => void): (() => void) => {
|
||||
const listener = (_event: unknown, entry: HistoryEntry): void => callback(entry);
|
||||
ipcRenderer.on(IPC_CHANNELS.HISTORY_ENTRY_ADDED, listener);
|
||||
return () => {
|
||||
ipcRenderer.removeListener(IPC_CHANNELS.HISTORY_ENTRY_ADDED, listener);
|
||||
};
|
||||
},
|
||||
clearHistory: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.CLEAR_HISTORY),
|
||||
removeHistoryEntry: (entryId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.REMOVE_HISTORY_ENTRY, entryId),
|
||||
revealHistoryEntry: (entryId: string): Promise<HistoryRevealResult> => ipcRenderer.invoke(IPC_CHANNELS.REVEAL_HISTORY_ENTRY, entryId),
|
||||
|
||||
+40
-1
@@ -73,6 +73,7 @@ import {
|
||||
} from "./views/collector/CollectorView";
|
||||
import {
|
||||
buildHistoryViewModel,
|
||||
mergeLiveHistoryEntry,
|
||||
pruneHistoryIds,
|
||||
selectVisibleHistoryIds,
|
||||
type HistoryFilter
|
||||
@@ -1658,6 +1659,8 @@ export function App(): ReactElement {
|
||||
const [historyCtxMenu, setHistoryCtxMenu] = useState<{ x: number; y: number; entryId: string } | null>(null);
|
||||
const historyCtxMenuRef = useRef<HTMLDivElement>(null);
|
||||
const historyLoadGenerationRef = useRef(0);
|
||||
const historyLoadActiveRef = useRef(false);
|
||||
const pendingLiveHistoryEntriesRef = useRef<HistoryEntry[]>([]);
|
||||
const historyVisibleIdsRef = useRef<string[]>([]);
|
||||
const [allDebridHostInfo, setAllDebridHostInfo] = useState<AllDebridHostInfo | null>(null);
|
||||
const [allDebridHostLoading, setAllDebridHostLoading] = useState(false);
|
||||
@@ -1807,6 +1810,8 @@ export function App(): ReactElement {
|
||||
|
||||
const loadHistoryEntries = useCallback(async (): Promise<void> => {
|
||||
const generation = ++historyLoadGenerationRef.current;
|
||||
historyLoadActiveRef.current = true;
|
||||
pendingLiveHistoryEntriesRef.current = [];
|
||||
setHistoryLoading(true);
|
||||
setHistoryError("");
|
||||
try {
|
||||
@@ -1814,13 +1819,24 @@ export function App(): ReactElement {
|
||||
if (!mountedRef.current || generation !== historyLoadGenerationRef.current) {
|
||||
return;
|
||||
}
|
||||
applyHistoryEntries(entries);
|
||||
const settings = snapshotRef.current.settings;
|
||||
const merged = [...pendingLiveHistoryEntriesRef.current].reverse().reduce(
|
||||
(current, entry) => mergeLiveHistoryEntry(current, entry, {
|
||||
maxEntries: settings.historyMaxEntries,
|
||||
maxAgeDays: settings.historyMaxAgeDays
|
||||
}),
|
||||
entries
|
||||
);
|
||||
pendingLiveHistoryEntriesRef.current = [];
|
||||
applyHistoryEntries(merged);
|
||||
} catch {
|
||||
if (mountedRef.current && generation === historyLoadGenerationRef.current) {
|
||||
setHistoryError("Verlauf konnte nicht geladen werden");
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current && generation === historyLoadGenerationRef.current) {
|
||||
historyLoadActiveRef.current = false;
|
||||
pendingLiveHistoryEntriesRef.current = [];
|
||||
setHistoryLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -1833,9 +1849,32 @@ export function App(): ReactElement {
|
||||
void loadHistoryEntries();
|
||||
return () => {
|
||||
historyLoadGenerationRef.current += 1;
|
||||
historyLoadActiveRef.current = false;
|
||||
pendingLiveHistoryEntriesRef.current = [];
|
||||
};
|
||||
}, [loadHistoryEntries, tab]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeHistoryEntryAdded = window.rd.onHistoryEntryAdded((entry) => {
|
||||
if (activeTabRef.current !== "history") {
|
||||
return;
|
||||
}
|
||||
if (historyLoadActiveRef.current) {
|
||||
pendingLiveHistoryEntriesRef.current = [
|
||||
entry,
|
||||
...pendingLiveHistoryEntriesRef.current.filter((pending) => pending.id !== entry.id)
|
||||
];
|
||||
}
|
||||
const settings = snapshotRef.current.settings;
|
||||
applyHistoryEntries(mergeLiveHistoryEntry(historyEntriesRef.current, entry, {
|
||||
maxEntries: settings.historyMaxEntries,
|
||||
maxAgeDays: settings.historyMaxAgeDays
|
||||
}));
|
||||
setHistoryError("");
|
||||
});
|
||||
return unsubscribeHistoryEntryAdded;
|
||||
}, [applyHistoryEntries]);
|
||||
|
||||
const loadAllDebridHostInfo = useCallback(async (silent = false): Promise<void> => {
|
||||
const requestId = allDebridHostRequestRef.current + 1;
|
||||
allDebridHostRequestRef.current = requestId;
|
||||
|
||||
@@ -50,6 +50,19 @@ export interface HistoryPage {
|
||||
|
||||
export const HISTORY_PAGE_SIZE = 100;
|
||||
|
||||
export function mergeLiveHistoryEntry(
|
||||
entries: readonly HistoryEntry[],
|
||||
incoming: HistoryEntry,
|
||||
limits: { maxEntries: number; maxAgeDays: number },
|
||||
nowMs: number = Date.now()
|
||||
): HistoryEntry[] {
|
||||
const maxEntries = limits.maxEntries > 0 ? Math.min(limits.maxEntries, 100_000) : 500;
|
||||
const cutoff = limits.maxAgeDays > 0 ? nowMs - limits.maxAgeDays * 86_400_000 : 0;
|
||||
const merged = [incoming, ...entries.filter((entry) => entry.id !== incoming.id)];
|
||||
const retained = cutoff > 0 ? merged.filter((entry) => entry.completedAt >= cutoff) : merged;
|
||||
return retained.length > maxEntries ? retained.slice(0, maxEntries) : retained;
|
||||
}
|
||||
|
||||
export const HISTORY_TABLE_COLUMN_IDS = ["name", "status", "size", "hoster", "started", "completed"] as const;
|
||||
export type HistoryTableColumnId = typeof HISTORY_TABLE_COLUMN_IDS[number];
|
||||
export type HistoryTableColumnWidths = Record<HistoryTableColumnId, number>;
|
||||
|
||||
@@ -79,6 +79,7 @@ export const IPC_CHANNELS = {
|
||||
EXTRACT_NOW: "queue:extract-now",
|
||||
RESET_PACKAGE: "queue:reset-package",
|
||||
GET_HISTORY: "history:get",
|
||||
HISTORY_ENTRY_ADDED: "history:entry-added",
|
||||
CLEAR_HISTORY: "history:clear",
|
||||
REMOVE_HISTORY_ENTRY: "history:remove-entry",
|
||||
REVEAL_HISTORY_ENTRY: "history:reveal-entry",
|
||||
|
||||
@@ -142,6 +142,7 @@ export interface ElectronApi {
|
||||
extractNow: (packageId: string) => Promise<void>;
|
||||
resetPackage: (packageId: string) => Promise<void>;
|
||||
getHistory: () => Promise<HistoryEntry[]>;
|
||||
onHistoryEntryAdded: (callback: (entry: HistoryEntry) => void) => () => void;
|
||||
clearHistory: () => Promise<void>;
|
||||
removeHistoryEntry: (entryId: string) => Promise<void>;
|
||||
revealHistoryEntry: (entryId: string) => Promise<HistoryRevealResult>;
|
||||
|
||||
Reference in New Issue
Block a user