Fix history timing and retention controls
This commit is contained in:
@@ -32,7 +32,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, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadSession, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, saveHistory, saveSession, saveSettings } from "./storage";
|
||||
import { addHistoryEntryForRetention, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistoryForRetention, loadSession, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, resetHistoryForRetention, saveHistory, saveSession, saveSettings } from "./storage";
|
||||
import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update";
|
||||
import { rotateDebugToken, startDebugServer, stopDebugServer } from "./debug-server";
|
||||
import { encryptBackup, decryptBackup } from "./backup-crypto";
|
||||
@@ -87,6 +87,7 @@ export class AppController {
|
||||
initRenameLog(this.storagePaths.baseDir);
|
||||
initTraceLog(this.storagePaths.baseDir);
|
||||
this.settings = loadSettings(this.storagePaths);
|
||||
resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
||||
const session = loadSession(this.storagePaths);
|
||||
this.megaWebFallback = new MegaWebFallback(() => ({
|
||||
login: this.settings.megaLogin,
|
||||
@@ -102,7 +103,7 @@ export class AppController {
|
||||
bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal),
|
||||
invalidateMegaSession: () => this.megaWebFallback.invalidateSession(),
|
||||
onHistoryEntry: (entry: HistoryEntry) => {
|
||||
addHistoryEntry(this.storagePaths, entry);
|
||||
addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry);
|
||||
}
|
||||
});
|
||||
this.manager.on("state", (snapshot: UiSnapshot) => {
|
||||
@@ -253,7 +254,11 @@ export class AppController {
|
||||
nextSettings.debridLinkApiKeyTotalUsageBytes = Object.fromEntries(
|
||||
Object.entries(liveSettings.debridLinkApiKeyTotalUsageBytes || {}).filter(([keyId]) => getDebridLinkApiKeyIds(nextSettings.debridLinkApiKeys).includes(keyId))
|
||||
);
|
||||
const retentionChanged = previousSettings.historyRetentionMode !== nextSettings.historyRetentionMode;
|
||||
this.settings = nextSettings;
|
||||
if (retentionChanged) {
|
||||
resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
||||
}
|
||||
saveSettings(this.storagePaths, this.settings);
|
||||
this.manager.setSettings(this.settings);
|
||||
this.audit("INFO", "Einstellungen aktualisiert", {
|
||||
@@ -532,7 +537,7 @@ export class AppController {
|
||||
public exportBackup(): Buffer {
|
||||
const settings = { ...this.settings };
|
||||
const session = this.manager.getSession();
|
||||
const history = loadHistory(this.storagePaths);
|
||||
const history = loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
||||
const payload = JSON.stringify({
|
||||
version: 2,
|
||||
appVersion: APP_VERSION,
|
||||
@@ -622,6 +627,8 @@ export class AppController {
|
||||
}
|
||||
}
|
||||
|
||||
resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
||||
|
||||
// Prevent prepareForShutdown from overwriting the restored data
|
||||
this.manager.skipShutdownPersist = true;
|
||||
this.manager.blockAllPersistence = true;
|
||||
@@ -664,11 +671,14 @@ export class AppController {
|
||||
this.audit("INFO", "App beendet");
|
||||
shutdownTraceLog();
|
||||
shutdownAuditLog();
|
||||
if (this.settings.historyRetentionMode === "session") {
|
||||
clearHistory(this.storagePaths);
|
||||
}
|
||||
logger.info("App beendet");
|
||||
}
|
||||
|
||||
public getHistory(): HistoryEntry[] {
|
||||
return loadHistory(this.storagePaths);
|
||||
return loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
||||
}
|
||||
|
||||
public clearHistory(): void {
|
||||
|
||||
@@ -97,6 +97,7 @@ export function defaultSettings(): AppSettings {
|
||||
minimizeToTray: false,
|
||||
theme: "dark" as const,
|
||||
collapseNewPackages: true,
|
||||
historyRetentionMode: "permanent",
|
||||
accountListShowDetailedDebridLinkKeys: false,
|
||||
autoSortPackagesByProgress: true,
|
||||
autoSkipExtracted: false,
|
||||
|
||||
@@ -2129,6 +2129,8 @@ export class DownloadManager extends EventEmitter {
|
||||
cancelled: false,
|
||||
enabled: true,
|
||||
priority: "normal",
|
||||
downloadStartedAt: 0,
|
||||
downloadCompletedAt: 0,
|
||||
createdAt: nowMs(),
|
||||
updatedAt: nowMs()
|
||||
};
|
||||
@@ -4958,8 +4960,10 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
item.progressPercent = 100;
|
||||
item.speedBps = 0;
|
||||
item.updatedAt = nowMs();
|
||||
pkg.updatedAt = nowMs();
|
||||
const finalizedAt = nowMs();
|
||||
item.updatedAt = finalizedAt;
|
||||
this.notePackageDownloadCompleted(pkg, finalizedAt);
|
||||
pkg.updatedAt = finalizedAt;
|
||||
this.recordRunOutcome(item.id, "completed");
|
||||
|
||||
if (this.session.running) {
|
||||
@@ -5732,6 +5736,27 @@ export class DownloadManager extends EventEmitter {
|
||||
void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (extractNow): ${compactErrorText(err)}`));
|
||||
}
|
||||
|
||||
private notePackageDownloadStarted(pkg: PackageEntry, startedAt = nowMs()): void {
|
||||
if ((pkg.downloadStartedAt || 0) <= 0) {
|
||||
pkg.downloadStartedAt = startedAt;
|
||||
}
|
||||
}
|
||||
|
||||
private notePackageDownloadCompleted(pkg: PackageEntry, completedAt = nowMs()): void {
|
||||
this.notePackageDownloadStarted(pkg, completedAt);
|
||||
pkg.downloadCompletedAt = Math.max(pkg.downloadCompletedAt || 0, completedAt);
|
||||
}
|
||||
|
||||
private getPackageHistoryDurationSeconds(pkg: PackageEntry): number {
|
||||
const startedAt = pkg.downloadStartedAt > 0 ? pkg.downloadStartedAt : pkg.createdAt;
|
||||
const finishedAtCandidate = pkg.downloadCompletedAt > 0 ? pkg.downloadCompletedAt : nowMs();
|
||||
const finishedAt = Math.max(startedAt || 0, finishedAtCandidate || 0);
|
||||
if (startedAt <= 0 || finishedAt <= 0) {
|
||||
return 1;
|
||||
}
|
||||
return Math.max(1, Math.floor((finishedAt - startedAt) / 1000));
|
||||
}
|
||||
|
||||
private recordPackageHistory(packageId: string, pkg: PackageEntry, items: DownloadItem[]): void {
|
||||
if (!this.onHistoryEntryCallback || this.historyRecordedPackages.has(packageId)) {
|
||||
return;
|
||||
@@ -5742,7 +5767,7 @@ export class DownloadManager extends EventEmitter {
|
||||
}
|
||||
this.historyRecordedPackages.add(packageId);
|
||||
const totalBytes = completedItems.reduce((sum, item) => sum + (item.downloadedBytes || 0), 0);
|
||||
const durationSeconds = pkg.createdAt > 0 ? Math.max(1, Math.floor((nowMs() - pkg.createdAt) / 1000)) : 1;
|
||||
const durationSeconds = this.getPackageHistoryDurationSeconds(pkg);
|
||||
const providers = new Set(completedItems.map(item => item.provider).filter(Boolean));
|
||||
const provider = providers.size === 1 ? [...providers][0] : null;
|
||||
const entry: HistoryEntry = {
|
||||
@@ -5776,7 +5801,7 @@ export class DownloadManager extends EventEmitter {
|
||||
const completedCount = completedItems.length;
|
||||
if (completedCount > 0) {
|
||||
const totalBytes = completedItems.reduce((sum, item) => sum + (item.downloadedBytes || 0), 0);
|
||||
const durationSeconds = pkg.createdAt > 0 ? Math.max(1, Math.floor((nowMs() - pkg.createdAt) / 1000)) : 1;
|
||||
const durationSeconds = this.getPackageHistoryDurationSeconds(pkg);
|
||||
const providers = new Set(completedItems.map(item => item.provider).filter(Boolean));
|
||||
const provider = providers.size === 1 ? [...providers][0] : null;
|
||||
const entry: HistoryEntry = {
|
||||
@@ -6759,6 +6784,7 @@ export class DownloadManager extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notePackageDownloadStarted(pkg);
|
||||
item.status = "validating";
|
||||
item.fullStatus = "Link wird umgewandelt";
|
||||
item.speedBps = 0;
|
||||
@@ -7077,14 +7103,16 @@ export class DownloadManager extends EventEmitter {
|
||||
throw new Error(`aborted:${active.abortReason}`);
|
||||
}
|
||||
|
||||
const completedAt = nowMs();
|
||||
item.status = "completed";
|
||||
item.fullStatus = this.settings.autoExtract
|
||||
? "Entpacken - Ausstehend"
|
||||
: `Fertig (${humanSize(item.downloadedBytes)})`;
|
||||
item.progressPercent = 100;
|
||||
item.speedBps = 0;
|
||||
item.updatedAt = nowMs();
|
||||
pkg.updatedAt = nowMs();
|
||||
item.updatedAt = completedAt;
|
||||
this.notePackageDownloadCompleted(pkg, completedAt);
|
||||
pkg.updatedAt = completedAt;
|
||||
this.recordRunOutcome(item.id, "completed");
|
||||
logger.info(`Download fertig: ${item.fileName} (${humanSize(item.downloadedBytes)}), pkg=${pkg.name}`);
|
||||
this.logPackageForItem(item, "INFO", "Download abgeschlossen", {
|
||||
|
||||
+25
-1
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import fsp from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys";
|
||||
import { AppSettings, BandwidthScheduleEntry, DebridFallbackProvider, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, PackageEntry, PackagePriority, SessionState } from "../shared/types";
|
||||
import { AppSettings, BandwidthScheduleEntry, DebridFallbackProvider, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, HistoryRetentionMode, PackageEntry, PackagePriority, SessionState } from "../shared/types";
|
||||
import { getProviderUsageDayKey } from "../shared/provider-daily-limits";
|
||||
import { defaultSettings } from "./constants";
|
||||
import { logger } from "./logger";
|
||||
@@ -15,6 +15,7 @@ const VALID_FINISHED_POLICIES = new Set(["never", "immediate", "on_start", "pack
|
||||
const VALID_SPEED_MODES = new Set(["global", "per_download"]);
|
||||
const VALID_THEMES = new Set(["dark", "light"]);
|
||||
const VALID_EXTRACT_CPU_PRIORITIES = new Set(["high", "middle", "low"]);
|
||||
const VALID_HISTORY_RETENTION_MODES = new Set<HistoryRetentionMode>(["never", "session", "permanent"]);
|
||||
const VALID_PACKAGE_PRIORITIES = new Set<string>(["high", "normal", "low"]);
|
||||
const VALID_DOWNLOAD_STATUSES = new Set<DownloadStatus>([
|
||||
"queued", "validating", "downloading", "paused", "reconnect_wait", "extracting", "integrity_check", "completed", "failed", "cancelled"
|
||||
@@ -375,6 +376,9 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
|
||||
clipboardWatch: Boolean(settings.clipboardWatch),
|
||||
minimizeToTray: Boolean(settings.minimizeToTray),
|
||||
collapseNewPackages: settings.collapseNewPackages !== undefined ? Boolean(settings.collapseNewPackages) : defaults.collapseNewPackages,
|
||||
historyRetentionMode: VALID_HISTORY_RETENTION_MODES.has(settings.historyRetentionMode)
|
||||
? settings.historyRetentionMode
|
||||
: defaults.historyRetentionMode,
|
||||
accountListShowDetailedDebridLinkKeys: settings.accountListShowDetailedDebridLinkKeys !== undefined
|
||||
? Boolean(settings.accountListShowDetailedDebridLinkKeys)
|
||||
: defaults.accountListShowDetailedDebridLinkKeys,
|
||||
@@ -582,6 +586,8 @@ export function normalizeLoadedSession(raw: unknown): SessionState {
|
||||
cancelled: Boolean(pkg.cancelled),
|
||||
enabled: pkg.enabled === undefined ? true : Boolean(pkg.enabled),
|
||||
priority: VALID_PACKAGE_PRIORITIES.has(asText(pkg.priority)) ? asText(pkg.priority) as PackagePriority : "normal",
|
||||
downloadStartedAt: clampNumber(pkg.downloadStartedAt, 0, 0, Number.MAX_SAFE_INTEGER),
|
||||
downloadCompletedAt: clampNumber(pkg.downloadCompletedAt, 0, 0, Number.MAX_SAFE_INTEGER),
|
||||
createdAt: clampNumber(pkg.createdAt, now, 0, Number.MAX_SAFE_INTEGER),
|
||||
updatedAt: clampNumber(pkg.updatedAt, now, 0, Number.MAX_SAFE_INTEGER)
|
||||
};
|
||||
@@ -1013,6 +1019,24 @@ export function addHistoryEntry(paths: StoragePaths, entry: HistoryEntry): Histo
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function loadHistoryForRetention(paths: StoragePaths, retentionMode: HistoryRetentionMode): HistoryEntry[] {
|
||||
return retentionMode === "never" ? [] : loadHistory(paths);
|
||||
}
|
||||
|
||||
export function addHistoryEntryForRetention(paths: StoragePaths, retentionMode: HistoryRetentionMode, entry: HistoryEntry): HistoryEntry[] {
|
||||
if (retentionMode === "never") {
|
||||
return [];
|
||||
}
|
||||
return addHistoryEntry(paths, entry);
|
||||
}
|
||||
|
||||
export function resetHistoryForRetention(paths: StoragePaths, retentionMode: HistoryRetentionMode): void {
|
||||
if (retentionMode === "permanent") {
|
||||
return;
|
||||
}
|
||||
clearHistory(paths);
|
||||
}
|
||||
|
||||
export function removeHistoryEntry(paths: StoragePaths, entryId: string): HistoryEntry[] {
|
||||
const existing = loadHistory(paths);
|
||||
const updated = existing.filter(e => e.id !== entryId);
|
||||
|
||||
Reference in New Issue
Block a user