Compare commits
2 Commits
8f3681b160
...
d594c5082b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d594c5082b | ||
|
|
6cde08dac3 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.224",
|
"version": "1.7.225",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import { getItemLogPath, initItemLogs, shutdownItemLogs } from "./item-log";
|
|||||||
import { getPackageLogPath, initPackageLogs, shutdownPackageLogs } from "./package-log";
|
import { getPackageLogPath, initPackageLogs, shutdownPackageLogs } from "./package-log";
|
||||||
import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log";
|
import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log";
|
||||||
import { MegaWebFallback } from "./mega-web-fallback";
|
import { MegaWebFallback } from "./mega-web-fallback";
|
||||||
import { addHistoryEntry, addHistoryEntryForRetention, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadHistoryForRetention, loadSession, 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, removeHistoryEntry, resetHistoryForRetention, saveHistory, saveSession, saveSettings } from "./storage";
|
||||||
import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update";
|
import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update";
|
||||||
import { runInstallWithResume } from "./update-install-flow";
|
import { runInstallWithResume } from "./update-install-flow";
|
||||||
import { rotateDebugToken, startDebugServer, stopDebugServer, restartDebugServer, getDebugServerRuntimeStatus, getActiveDebugToken, getDebugAllowlist, writeDebugServerConfig, clearDebugToken } from "./debug-server";
|
import { rotateDebugToken, startDebugServer, stopDebugServer, restartDebugServer, getDebugServerRuntimeStatus, getActiveDebugToken, getDebugAllowlist, writeDebugServerConfig, clearDebugToken } from "./debug-server";
|
||||||
@ -112,7 +112,8 @@ export class AppController {
|
|||||||
initTraceLog(this.storagePaths.baseDir);
|
initTraceLog(this.storagePaths.baseDir);
|
||||||
this.settings = loadSettings(this.storagePaths);
|
this.settings = loadSettings(this.storagePaths);
|
||||||
resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
|
||||||
const session = loadSession(this.storagePaths);
|
const loadResult = loadSessionWithStatus(this.storagePaths);
|
||||||
|
const session = loadResult.session;
|
||||||
this.megaWebFallback = new MegaWebFallback(() => ({
|
this.megaWebFallback = new MegaWebFallback(() => ({
|
||||||
login: this.settings.megaLogin,
|
login: this.settings.megaLogin,
|
||||||
password: this.settings.megaPassword
|
password: this.settings.megaPassword
|
||||||
@ -126,6 +127,7 @@ export class AppController {
|
|||||||
realDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.realDebridWebFallback.unrestrict(link, signal),
|
realDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.realDebridWebFallback.unrestrict(link, signal),
|
||||||
bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal),
|
bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal),
|
||||||
invalidateMegaSession: () => this.megaWebFallback.invalidateSession(),
|
invalidateMegaSession: () => this.megaWebFallback.invalidateSession(),
|
||||||
|
protectEmptyClobber: loadResult.status === "empty-unreadable",
|
||||||
onHistoryEntry: (entry: HistoryEntry) => {
|
onHistoryEntry: (entry: HistoryEntry) => {
|
||||||
addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry, this.historyLimits());
|
addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry, this.historyLimits());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -363,6 +363,7 @@ type DownloadManagerOptions = {
|
|||||||
bestDebridWebUnrestrict?: BestDebridWebUnrestrictor;
|
bestDebridWebUnrestrict?: BestDebridWebUnrestrictor;
|
||||||
invalidateMegaSession?: () => void;
|
invalidateMegaSession?: () => void;
|
||||||
onHistoryEntry?: HistoryEntryCallback;
|
onHistoryEntry?: HistoryEntryCallback;
|
||||||
|
protectEmptyClobber?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateHistoryId(): string {
|
function generateHistoryId(): string {
|
||||||
@ -1688,6 +1689,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
public blockAllPersistence = false;
|
public blockAllPersistence = false;
|
||||||
|
|
||||||
|
private protectAgainstEmptyClobber = false;
|
||||||
|
|
||||||
|
private emptyClobberProtectionLogged = false;
|
||||||
|
|
||||||
private debridService: DebridService;
|
private debridService: DebridService;
|
||||||
|
|
||||||
private invalidateMegaSessionFn?: () => void;
|
private invalidateMegaSessionFn?: () => void;
|
||||||
@ -1831,6 +1836,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.session = session;
|
this.session = session;
|
||||||
this.itemCount = Object.keys(this.session.items).length;
|
this.itemCount = Object.keys(this.session.items).length;
|
||||||
this.storagePaths = storagePaths;
|
this.storagePaths = storagePaths;
|
||||||
|
this.protectAgainstEmptyClobber = Boolean(options.protectEmptyClobber);
|
||||||
|
if (this.protectAgainstEmptyClobber) {
|
||||||
|
logger.warn("Session-Schutz aktiv: Start mit unlesbarer Session — leere Speicherungen blockiert, bis echte Daten vorliegen");
|
||||||
|
}
|
||||||
this.debridService = new DebridService(settings, {
|
this.debridService = new DebridService(settings, {
|
||||||
megaWebUnrestrict: options.megaWebUnrestrict,
|
megaWebUnrestrict: options.megaWebUnrestrict,
|
||||||
allDebridWebUnrestrict: options.allDebridWebUnrestrict,
|
allDebridWebUnrestrict: options.allDebridWebUnrestrict,
|
||||||
@ -5821,7 +5830,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const itemCount = Object.keys(this.session.items).length;
|
const itemCount = Object.keys(this.session.items).length;
|
||||||
logger.info(`Shutdown-Save: ${pkgCount} Pakete, ${itemCount} Items`);
|
logger.info(`Shutdown-Save: ${pkgCount} Pakete, ${itemCount} Items`);
|
||||||
this.foldRuntimeIntoSettings(nowMs());
|
this.foldRuntimeIntoSettings(nowMs());
|
||||||
|
if (!this.guardBlocksSessionSave()) {
|
||||||
saveSession(this.storagePaths, this.session);
|
saveSession(this.storagePaths, this.session);
|
||||||
|
}
|
||||||
saveSettings(this.storagePaths, this.settings);
|
saveSettings(this.storagePaths, this.settings);
|
||||||
} else {
|
} else {
|
||||||
logger.info(`Shutdown-Save übersprungen: skipShutdownPersist=${this.skipShutdownPersist}, blockAllPersistence=${this.blockAllPersistence}`);
|
logger.info(`Shutdown-Save übersprungen: skipShutdownPersist=${this.skipShutdownPersist}, blockAllPersistence=${this.blockAllPersistence}`);
|
||||||
@ -6124,10 +6135,29 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private guardBlocksSessionSave(): boolean {
|
||||||
|
if (!this.protectAgainstEmptyClobber) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const isEmpty = Object.keys(this.session.packages).length === 0 && Object.keys(this.session.items).length === 0;
|
||||||
|
if (isEmpty) {
|
||||||
|
if (!this.emptyClobberProtectionLogged) {
|
||||||
|
logger.warn("Leere Session-Speicherung uebersprungen (Schutz nach unlesbarem Start) — vorhandene Datei bleibt unangetastet");
|
||||||
|
this.emptyClobberProtectionLogged = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.protectAgainstEmptyClobber = false;
|
||||||
|
logger.info("Session-Schutz aufgehoben: nicht-leere Session wird wieder normal gespeichert");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private persistNow(): void {
|
private persistNow(): void {
|
||||||
const now = nowMs();
|
const now = nowMs();
|
||||||
this.lastPersistAt = now;
|
this.lastPersistAt = now;
|
||||||
|
if (!this.guardBlocksSessionSave()) {
|
||||||
void saveSessionAsync(this.storagePaths, this.session).catch((err) => logger.warn(`saveSessionAsync Fehler: ${compactErrorText(err)}`));
|
void saveSessionAsync(this.storagePaths, this.session).catch((err) => logger.warn(`saveSessionAsync Fehler: ${compactErrorText(err)}`));
|
||||||
|
}
|
||||||
if (now - this.lastSettingsPersistAt >= 30000) {
|
if (now - this.lastSettingsPersistAt >= 30000) {
|
||||||
this.foldRuntimeIntoSettings(now);
|
this.foldRuntimeIntoSettings(now);
|
||||||
this.lastSettingsPersistAt = now;
|
this.lastSettingsPersistAt = now;
|
||||||
@ -6141,7 +6171,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const itemCount = Object.keys(this.session.items).length;
|
const itemCount = Object.keys(this.session.items).length;
|
||||||
logger.info(`Pre-Update Sync-Save: ${pkgCount} Pakete, ${itemCount} Items`);
|
logger.info(`Pre-Update Sync-Save: ${pkgCount} Pakete, ${itemCount} Items`);
|
||||||
this.foldRuntimeIntoSettings(nowMs());
|
this.foldRuntimeIntoSettings(nowMs());
|
||||||
|
if (!this.guardBlocksSessionSave()) {
|
||||||
saveSession(this.storagePaths, this.session);
|
saveSession(this.storagePaths, this.session);
|
||||||
|
}
|
||||||
saveSettings(this.storagePaths, this.settings);
|
saveSettings(this.storagePaths, this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -890,21 +890,50 @@ export function normalizeLoadedSessionTransientFields(session: SessionState): Se
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TRANSIENT_READ_CODES = new Set(["EBUSY", "EPERM", "EAGAIN"]);
|
||||||
|
|
||||||
|
function sleepSyncMs(ms: number): void {
|
||||||
|
if (ms <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
||||||
|
}
|
||||||
|
|
||||||
function readSessionFile(filePath: string): SessionState | null {
|
function readSessionFile(filePath: string): SessionState | null {
|
||||||
|
let raw: string | null = null;
|
||||||
|
const maxAttempts = 5;
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")) as unknown;
|
raw = fs.readFileSync(filePath, "utf8");
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
const code = (error as NodeJS.ErrnoException)?.code || "";
|
||||||
|
if (TRANSIENT_READ_CODES.has(code) && attempt < maxAttempts) {
|
||||||
|
const backoffMs = 100 * 2 ** (attempt - 1);
|
||||||
|
logger.warn(`Session-Datei vorübergehend gesperrt (${code}), Versuch ${attempt}/${maxAttempts}, warte ${backoffMs}ms: ${filePath}`);
|
||||||
|
sleepSyncMs(backoffMs);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code === "EACCES" || code === "EPERM") {
|
||||||
|
logger.error(`Session-Datei nicht zugreifbar (${code}): ${filePath} - pruefe Datei-/Ordner-Berechtigungen fuer Benutzer ${process.env.USERNAME || process.env.USER || "?"}`);
|
||||||
|
} else {
|
||||||
|
logger.error(`Session-Datei nicht lesbar (${code || "?"}): ${filePath}: ${String(error)}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (raw === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw) as unknown;
|
||||||
const session = normalizeLoadedSessionTransientFields(normalizeLoadedSession(parsed));
|
const session = normalizeLoadedSessionTransientFields(normalizeLoadedSession(parsed));
|
||||||
const pkgCount = Object.keys(session.packages).length;
|
const pkgCount = Object.keys(session.packages).length;
|
||||||
const itemCount = Object.keys(session.items).length;
|
const itemCount = Object.keys(session.items).length;
|
||||||
logger.info(`Session geladen: ${filePath} (${pkgCount} Pakete, ${itemCount} Items)`);
|
logger.info(`Session geladen: ${filePath} (${pkgCount} Pakete, ${itemCount} Items)`);
|
||||||
return session;
|
return session;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const code = (error as NodeJS.ErrnoException)?.code || "";
|
logger.error(`Session-Datei beschädigt (JSON ungültig): ${filePath}: ${String(error)}`);
|
||||||
if (code === "EACCES" || code === "EPERM") {
|
|
||||||
logger.error(`Session-Datei nicht zugreifbar (${code}): ${filePath} - pruefe Datei-/Ordner-Berechtigungen fuer Benutzer ${process.env.USERNAME || process.env.USER || "?"}`);
|
|
||||||
} else {
|
|
||||||
logger.error(`Session-Datei nicht lesbar: ${filePath}: ${String(error)}`);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1004,17 +1033,31 @@ export function emptySession(): SessionState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadSession(paths: StoragePaths): SessionState {
|
export type SessionLoadStatus =
|
||||||
|
| "ok"
|
||||||
|
| "recovered-backup"
|
||||||
|
| "recovered-temp"
|
||||||
|
| "empty-fresh"
|
||||||
|
| "empty-unreadable";
|
||||||
|
|
||||||
|
export interface SessionLoadResult {
|
||||||
|
session: SessionState;
|
||||||
|
status: SessionLoadStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadSessionWithStatus(paths: StoragePaths): SessionLoadResult {
|
||||||
ensureBaseDir(paths.baseDir);
|
ensureBaseDir(paths.baseDir);
|
||||||
const backupFile = sessionBackupPath(paths.sessionFile);
|
const backupFile = sessionBackupPath(paths.sessionFile);
|
||||||
|
const syncTempFile = sessionTempPath(paths.sessionFile, "sync");
|
||||||
|
const asyncTempFile = sessionTempPath(paths.sessionFile, "async");
|
||||||
const primaryExists = fs.existsSync(paths.sessionFile);
|
const primaryExists = fs.existsSync(paths.sessionFile);
|
||||||
|
const backupExists = fs.existsSync(backupFile);
|
||||||
|
const anyTempExists = fs.existsSync(syncTempFile) || fs.existsSync(asyncTempFile);
|
||||||
|
|
||||||
if (!primaryExists) {
|
if (!primaryExists) {
|
||||||
const hasRecoverable = fs.existsSync(backupFile)
|
if (!backupExists && !anyTempExists) {
|
||||||
|| fs.existsSync(sessionTempPath(paths.sessionFile, "sync"))
|
|
||||||
|| fs.existsSync(sessionTempPath(paths.sessionFile, "async"));
|
|
||||||
if (!hasRecoverable) {
|
|
||||||
logger.info("Keine Session-Datei vorhanden, starte mit leerer Session");
|
logger.info("Keine Session-Datei vorhanden, starte mit leerer Session");
|
||||||
return emptySession();
|
return { session: emptySession(), status: "empty-fresh" };
|
||||||
}
|
}
|
||||||
logger.warn("Session-Primaerdatei fehlt, aber Backup/Temp vorhanden — Wiederherstellung wird versucht");
|
logger.warn("Session-Primaerdatei fehlt, aber Backup/Temp vorhanden — Wiederherstellung wird versucht");
|
||||||
}
|
}
|
||||||
@ -1023,7 +1066,7 @@ export function loadSession(paths: StoragePaths): SessionState {
|
|||||||
|
|
||||||
if (primary) {
|
if (primary) {
|
||||||
const primaryPkgCount = Object.keys(primary.packages).length;
|
const primaryPkgCount = Object.keys(primary.packages).length;
|
||||||
if (primaryPkgCount === 0 && fs.existsSync(backupFile)) {
|
if (primaryPkgCount === 0 && backupExists) {
|
||||||
const backup = readSessionFile(backupFile);
|
const backup = readSessionFile(backupFile);
|
||||||
if (backup) {
|
if (backup) {
|
||||||
const backupPkgCount = Object.keys(backup.packages).length;
|
const backupPkgCount = Object.keys(backup.packages).length;
|
||||||
@ -1031,29 +1074,27 @@ export function loadSession(paths: StoragePaths): SessionState {
|
|||||||
logger.warn(`Session-Datei ist leer (0 Pakete), aber Backup hat ${backupPkgCount} Pakete — verwende Backup`);
|
logger.warn(`Session-Datei ist leer (0 Pakete), aber Backup hat ${backupPkgCount} Pakete — verwende Backup`);
|
||||||
try {
|
try {
|
||||||
const payload = JSON.stringify({ ...backup, updatedAt: Date.now() }, safeJsonReplacer);
|
const payload = JSON.stringify({ ...backup, updatedAt: Date.now() }, safeJsonReplacer);
|
||||||
const tempPath = sessionTempPath(paths.sessionFile, "sync");
|
fs.writeFileSync(syncTempFile, payload, "utf8");
|
||||||
fs.writeFileSync(tempPath, payload, "utf8");
|
syncRenameWithExdevFallback(syncTempFile, paths.sessionFile);
|
||||||
syncRenameWithExdevFallback(tempPath, paths.sessionFile);
|
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
return backup;
|
return { session: backup, status: "recovered-backup" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return primary;
|
return { session: primary, status: "ok" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const backup = fs.existsSync(backupFile) ? readSessionFile(backupFile) : null;
|
const backup = backupExists ? readSessionFile(backupFile) : null;
|
||||||
if (backup) {
|
if (backup) {
|
||||||
logger.warn("Session defekt, Backup-Datei wird verwendet");
|
logger.warn("Session defekt, Backup-Datei wird verwendet");
|
||||||
try {
|
try {
|
||||||
const payload = JSON.stringify({ ...backup, updatedAt: Date.now() }, safeJsonReplacer);
|
const payload = JSON.stringify({ ...backup, updatedAt: Date.now() }, safeJsonReplacer);
|
||||||
const tempPath = sessionTempPath(paths.sessionFile, "sync");
|
fs.writeFileSync(syncTempFile, payload, "utf8");
|
||||||
fs.writeFileSync(tempPath, payload, "utf8");
|
syncRenameWithExdevFallback(syncTempFile, paths.sessionFile);
|
||||||
syncRenameWithExdevFallback(tempPath, paths.sessionFile);
|
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
return backup;
|
return { session: backup, status: "recovered-backup" };
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const kind of ["sync", "async"] as const) {
|
for (const kind of ["sync", "async"] as const) {
|
||||||
@ -1067,13 +1108,21 @@ export function loadSession(paths: StoragePaths): SessionState {
|
|||||||
fs.writeFileSync(paths.sessionFile, payload, "utf8");
|
fs.writeFileSync(paths.sessionFile, payload, "utf8");
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
return tmpSession;
|
return { session: tmpSession, status: "recovered-temp" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error("Session konnte nicht geladen werden (Primary, Backup und Temp-Dateien fehlgeschlagen)");
|
if (primaryExists || backupExists || anyTempExists) {
|
||||||
return emptySession();
|
logger.error("Session konnte nicht geladen werden (Primary, Backup und Temp-Dateien fehlgeschlagen) — Schutz gegen leeres Ueberschreiben aktiv");
|
||||||
|
return { session: emptySession(), status: "empty-unreadable" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { session: emptySession(), status: "empty-fresh" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadSession(paths: StoragePaths): SessionState {
|
||||||
|
return loadSessionWithStatus(paths).session;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveSession(paths: StoragePaths, session: SessionState): void {
|
export function saveSession(paths: StoragePaths, session: SessionState): void {
|
||||||
@ -1088,7 +1137,13 @@ export function saveSession(paths: StoragePaths, session: SessionState): void {
|
|||||||
const payload = JSON.stringify({ ...session, updatedAt: Date.now() }, safeJsonReplacer);
|
const payload = JSON.stringify({ ...session, updatedAt: Date.now() }, safeJsonReplacer);
|
||||||
const tempPath = sessionTempPath(paths.sessionFile, "sync");
|
const tempPath = sessionTempPath(paths.sessionFile, "sync");
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(tempPath, payload, "utf8");
|
const fd = fs.openSync(tempPath, "w");
|
||||||
|
try {
|
||||||
|
fs.writeSync(fd, payload);
|
||||||
|
fs.fsyncSync(fd);
|
||||||
|
} finally {
|
||||||
|
fs.closeSync(fd);
|
||||||
|
}
|
||||||
syncRenameWithExdevFallback(tempPath, paths.sessionFile);
|
syncRenameWithExdevFallback(tempPath, paths.sessionFile);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try { fs.rmSync(tempPath, { force: true }); } catch { }
|
try { fs.rmSync(tempPath, { force: true }); } catch { }
|
||||||
@ -1104,7 +1159,13 @@ async function writeSessionPayload(paths: StoragePaths, payload: string, generat
|
|||||||
await fs.promises.mkdir(paths.baseDir, { recursive: true });
|
await fs.promises.mkdir(paths.baseDir, { recursive: true });
|
||||||
await fsp.copyFile(paths.sessionFile, sessionBackupPath(paths.sessionFile)).catch(() => {});
|
await fsp.copyFile(paths.sessionFile, sessionBackupPath(paths.sessionFile)).catch(() => {});
|
||||||
const tempPath = sessionTempPath(paths.sessionFile, "async");
|
const tempPath = sessionTempPath(paths.sessionFile, "async");
|
||||||
await fsp.writeFile(tempPath, payload, "utf8");
|
const handle = await fsp.open(tempPath, "w");
|
||||||
|
try {
|
||||||
|
await handle.writeFile(payload, "utf8");
|
||||||
|
await handle.sync();
|
||||||
|
} finally {
|
||||||
|
await handle.close();
|
||||||
|
}
|
||||||
if (generation < syncSaveGeneration) {
|
if (generation < syncSaveGeneration) {
|
||||||
await fsp.rm(tempPath, { force: true }).catch(() => {});
|
await fsp.rm(tempPath, { force: true }).catch(() => {});
|
||||||
return;
|
return;
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user