fix(history): remove selections atomically
This commit is contained in:
@@ -53,7 +53,7 @@ import { getItemLogPath, initItemLogs, shutdownItemLogs } from "./item-log";
|
||||
import { getPackageLogPath, initPackageLogs, shutdownPackageLogs } from "./package-log";
|
||||
import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log";
|
||||
import { MegaWebFallback } from "./mega-web-fallback";
|
||||
import { addHistoryEntry, addHistoryEntryForRetention, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadHistoryForRetention, loadSessionWithStatus, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, resetHistoryForRetention, saveHistory, saveSession, saveSettings } from "./storage";
|
||||
import { addHistoryEntry, addHistoryEntryForRetention, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadHistoryForRetention, loadSessionWithStatus, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntries, resetHistoryForRetention, saveHistory, saveSession, saveSettings } from "./storage";
|
||||
import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update";
|
||||
import { runInstallWithResume } from "./update-install-flow";
|
||||
import { rotateDebugToken, startDebugServer, stopDebugServer, restartDebugServer, getDebugServerRuntimeStatus, getActiveDebugToken, getDebugAllowlist, writeDebugServerConfig, clearDebugToken } from "./debug-server";
|
||||
@@ -1381,10 +1381,14 @@ export class AppController {
|
||||
this.manager.resetItems(itemIds);
|
||||
}
|
||||
|
||||
public removeHistoryEntry(entryId: string): void {
|
||||
this.audit("INFO", "Verlaufseintrag entfernt", { entryId });
|
||||
removeHistoryEntry(this.storagePaths, entryId);
|
||||
}
|
||||
public removeHistoryEntry(entryId: string): void {
|
||||
this.removeHistoryEntries([entryId]);
|
||||
}
|
||||
|
||||
public removeHistoryEntries(entryIds: string[]): void {
|
||||
this.audit("INFO", "Verlaufseinträge entfernt", { count: entryIds.length });
|
||||
removeHistoryEntries(this.storagePaths, entryIds, this.historyLimits());
|
||||
}
|
||||
|
||||
public addToHistory(entry: HistoryEntry): void {
|
||||
this.audit("INFO", "Verlaufseintrag hinzugefügt", {
|
||||
|
||||
@@ -97,6 +97,14 @@ function isDevMode(): boolean {
|
||||
return process.env.NODE_ENV === "development";
|
||||
}
|
||||
|
||||
function validateHistoryEntryIds(value: unknown): string[] {
|
||||
const entryIds = validateStringArray(value, "entryIds");
|
||||
if (entryIds.length > 100_000 || entryIds.some((entryId) => entryId.length === 0 || entryId.length > 4_096)) {
|
||||
throw new Error("entryIds ist ungültig");
|
||||
}
|
||||
return [...new Set(entryIds)];
|
||||
}
|
||||
|
||||
function getRendererFileUrl(): string {
|
||||
return pathToFileURL(path.join(app.getAppPath(), "build", "renderer", "index.html")).toString();
|
||||
}
|
||||
@@ -588,6 +596,9 @@ function registerIpcHandlers(): void {
|
||||
validateString(entryId, "entryId");
|
||||
return controller.removeHistoryEntry(entryId);
|
||||
});
|
||||
handleTrusted(IPC_CHANNELS.REMOVE_HISTORY_ENTRIES, (_event: IpcMainInvokeEvent, entryIds: unknown) => {
|
||||
return controller.removeHistoryEntries(validateHistoryEntryIds(entryIds));
|
||||
});
|
||||
handleTrusted(IPC_CHANNELS.REVEAL_HISTORY_ENTRY, (_event: IpcMainInvokeEvent, entryId: unknown) => {
|
||||
return revealHistoryEntry({ entryId }, {
|
||||
loadHistory: () => controller.getHistory(),
|
||||
|
||||
+16
-6
@@ -1546,12 +1546,22 @@ export function resetHistoryForRetention(paths: StoragePaths, retentionMode: His
|
||||
clearHistory(paths);
|
||||
}
|
||||
|
||||
export function removeHistoryEntry(paths: StoragePaths, entryId: string): HistoryEntry[] {
|
||||
const existing = loadHistory(paths);
|
||||
const updated = existing.filter(e => e.id !== entryId);
|
||||
saveHistory(paths, updated);
|
||||
return updated;
|
||||
}
|
||||
export function removeHistoryEntries(paths: StoragePaths, entryIds: readonly string[], limits?: HistoryLimits): HistoryEntry[] {
|
||||
const existing = loadHistory(paths, limits);
|
||||
const removedIds = new Set(entryIds);
|
||||
if (removedIds.size === 0) {
|
||||
return existing;
|
||||
}
|
||||
const updated = existing.filter((entry) => !removedIds.has(entry.id));
|
||||
if (updated.length !== existing.length) {
|
||||
saveHistory(paths, updated, limits);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function removeHistoryEntry(paths: StoragePaths, entryId: string, limits?: HistoryLimits): HistoryEntry[] {
|
||||
return removeHistoryEntries(paths, [entryId], limits);
|
||||
}
|
||||
|
||||
export function clearHistory(paths: StoragePaths): void {
|
||||
ensureBaseDir(paths.baseDir);
|
||||
|
||||
@@ -124,6 +124,7 @@ const api: ElectronApi = {
|
||||
},
|
||||
clearHistory: (): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.CLEAR_HISTORY),
|
||||
removeHistoryEntry: (entryId: string): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.REMOVE_HISTORY_ENTRY, entryId),
|
||||
removeHistoryEntries: (entryIds: string[]): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.REMOVE_HISTORY_ENTRIES, entryIds),
|
||||
revealHistoryEntry: (entryId: string): Promise<HistoryRevealResult> => ipcRenderer.invoke(IPC_CHANNELS.REVEAL_HISTORY_ENTRY, entryId),
|
||||
setPackagePriority: (packageId: string, priority: PackagePriority): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.SET_PACKAGE_PRIORITY, packageId, priority),
|
||||
skipItems: (itemIds: string[]): Promise<void> => ipcRenderer.invoke(IPC_CHANNELS.SKIP_ITEMS, itemIds),
|
||||
|
||||
@@ -3285,9 +3285,10 @@ export function App(): ReactElement {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
const results = await Promise.allSettled(ids.map((id) => window.rd.removeHistoryEntry(id)));
|
||||
if (results.some((result) => result.status === "rejected")) {
|
||||
showToast("Einige Verlaufseinträge konnten nicht entfernt werden");
|
||||
try {
|
||||
await window.rd.removeHistoryEntries(ids);
|
||||
} catch {
|
||||
showToast(ids.length === 1 ? "Verlaufseintrag konnte nicht entfernt werden" : "Verlaufseinträge konnten nicht entfernt werden");
|
||||
await loadHistoryEntries();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export const IPC_CHANNELS = {
|
||||
HISTORY_ENTRY_ADDED: "history:entry-added",
|
||||
CLEAR_HISTORY: "history:clear",
|
||||
REMOVE_HISTORY_ENTRY: "history:remove-entry",
|
||||
REMOVE_HISTORY_ENTRIES: "history:remove-entries",
|
||||
REVEAL_HISTORY_ENTRY: "history:reveal-entry",
|
||||
SET_PACKAGE_PRIORITY: "queue:set-package-priority",
|
||||
SKIP_ITEMS: "queue:skip-items",
|
||||
|
||||
@@ -145,6 +145,7 @@ export interface ElectronApi {
|
||||
onHistoryEntryAdded: (callback: (entry: HistoryEntry) => void) => () => void;
|
||||
clearHistory: () => Promise<void>;
|
||||
removeHistoryEntry: (entryId: string) => Promise<void>;
|
||||
removeHistoryEntries: (entryIds: string[]) => Promise<void>;
|
||||
revealHistoryEntry: (entryId: string) => Promise<HistoryRevealResult>;
|
||||
setPackagePriority: (packageId: string, priority: PackagePriority) => Promise<void>;
|
||||
skipItems: (itemIds: string[]) => Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user