Initial clean project import
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||
import { BrowserWindow } from "electron";
|
||||
import { fetchElectronSystemConfig } from "../systemConfig";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import Log from "../log/main";
|
||||
import { checkResourceBundles, setResourceStatusListener } from "../resourceManager";
|
||||
import { ProcessCleanupManager } from "../../lib/process-cleanup";
|
||||
import { isDevelopment } from "../../lib/resource-path";
|
||||
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.disableDifferentialDownload = false;
|
||||
(autoUpdater as any).logger = {
|
||||
info: (...args: any[]) => Log.info("[UpdaterCore]", args),
|
||||
warn: (...args: any[]) => Log.warn("[UpdaterCore]", args),
|
||||
error: (...args: any[]) => Log.error("[UpdaterCore]", args),
|
||||
debug: (...args: any[]) => Log.debug("[UpdaterCore]", args),
|
||||
};
|
||||
|
||||
let updaterWindow: BrowserWindow | null = null;
|
||||
let downloadedSetupPath: string | null = null;
|
||||
let latestUpdaterStatus: any = null;
|
||||
|
||||
export function setUpdaterWindow(win: BrowserWindow) {
|
||||
updaterWindow = win;
|
||||
}
|
||||
|
||||
function sendToRenderer(channel: string, ...args: any[]) {
|
||||
if (channel === "updater:status" && args.length > 0) {
|
||||
latestUpdaterStatus = args[0];
|
||||
}
|
||||
if (updaterWindow && !updaterWindow.isDestroyed()) {
|
||||
updaterWindow.webContents.send(channel, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
const getLatestStatus = () => latestUpdaterStatus;
|
||||
|
||||
setResourceStatusListener((data) => {
|
||||
sendToRenderer("updater:status", data);
|
||||
});
|
||||
|
||||
async function ensureUpdateFeedURL() {
|
||||
try {
|
||||
const config = await fetchElectronSystemConfig();
|
||||
if (config.update_url) {
|
||||
autoUpdater.setFeedURL({ provider: "generic", url: config.update_url });
|
||||
Log.info("[Updater] feedURL set:", config.update_url);
|
||||
}
|
||||
} catch (e: any) {
|
||||
Log.error("[Updater] setFeedURL failed:", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
autoUpdater.on("checking-for-update", () => {
|
||||
Log.info("[Updater] checking for update...");
|
||||
sendToRenderer("updater:status", { status: "checking" });
|
||||
});
|
||||
|
||||
autoUpdater.on("update-available", (info: UpdateInfo) => {
|
||||
Log.info("[Updater] update available:", {
|
||||
version: info.version,
|
||||
files: info.files?.map(file => file.url) || [],
|
||||
});
|
||||
sendToRenderer("updater:status", {
|
||||
status: "available",
|
||||
version: info.version,
|
||||
releaseNotes: info.releaseNotes,
|
||||
fileSize: info.files?.[0]?.size || 0,
|
||||
});
|
||||
});
|
||||
|
||||
autoUpdater.on("update-not-available", () => {
|
||||
Log.info("[Updater] no update available");
|
||||
sendToRenderer("updater:status", { status: "not-available" });
|
||||
});
|
||||
|
||||
autoUpdater.on("download-progress", (progress) => {
|
||||
sendToRenderer("updater:status", {
|
||||
status: "downloading",
|
||||
percent: Math.round(progress.percent),
|
||||
bytesPerSecond: progress.bytesPerSecond,
|
||||
transferred: progress.transferred,
|
||||
total: progress.total,
|
||||
});
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", () => {
|
||||
try {
|
||||
const helper = (autoUpdater as any).downloadedUpdateHelper;
|
||||
if (helper?.cacheDir) {
|
||||
const pendingDir = helper.cacheDirForPendingUpdate || path.join(helper.cacheDir, "pending");
|
||||
const pendingInstallerPath = (() => {
|
||||
try {
|
||||
if (!fs.existsSync(pendingDir)) {
|
||||
return null;
|
||||
}
|
||||
const pendingFiles = fs.readdirSync(pendingDir).filter((f: string) => f.endsWith(".exe")).sort();
|
||||
if (pendingFiles.length > 0) {
|
||||
return path.join(pendingDir, pendingFiles[pendingFiles.length - 1]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
Log.warn("[Updater] read pending installer failed:", error?.message || error);
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
if (pendingInstallerPath && fs.existsSync(pendingInstallerPath)) {
|
||||
downloadedSetupPath = pendingInstallerPath;
|
||||
} else {
|
||||
const files = fs.readdirSync(helper.cacheDir).filter((f: string) => f.endsWith(".exe")).sort();
|
||||
if (files.length > 0) {
|
||||
downloadedSetupPath = path.join(helper.cacheDir, files[files.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a stable old installer cache so the next update can reuse it
|
||||
// for differential download instead of falling back to a full package.
|
||||
if (downloadedSetupPath && fs.existsSync(downloadedSetupPath)) {
|
||||
const canonicalInstallerPath = path.join(helper.cacheDir, "installer.exe");
|
||||
if (path.resolve(downloadedSetupPath) !== path.resolve(canonicalInstallerPath)) {
|
||||
fs.copyFileSync(downloadedSetupPath, canonicalInstallerPath);
|
||||
Log.info("[Updater] cached canonical installer for future differential download:", canonicalInstallerPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.info("[Updater] downloaded, setup path:", downloadedSetupPath);
|
||||
} catch (e: any) {
|
||||
Log.error("[Updater] find downloaded exe failed:", e.message);
|
||||
}
|
||||
sendToRenderer("updater:status", { status: "downloaded" });
|
||||
});
|
||||
|
||||
autoUpdater.on("error", (error) => {
|
||||
Log.error("[Updater] error:", error?.message || error);
|
||||
sendToRenderer("updater:status", { status: "error", message: error?.message || "Unknown error" });
|
||||
});
|
||||
|
||||
const checkForUpdate = async () => {
|
||||
try {
|
||||
if (!isDevelopment()) {
|
||||
await ensureUpdateFeedURL();
|
||||
sendToRenderer("updater:status", { status: "resource-checking" });
|
||||
const resourceCheck = await checkResourceBundles({ installMissing: false });
|
||||
sendToRenderer("updater:status", {
|
||||
status: "resource-check-complete",
|
||||
pending: resourceCheck.pending,
|
||||
missingRequired: resourceCheck.missingRequired,
|
||||
installed: resourceCheck.installed,
|
||||
});
|
||||
await autoUpdater.checkForUpdates();
|
||||
}
|
||||
return { code: 0, msg: "success" };
|
||||
} catch (e: any) {
|
||||
Log.error("[Updater] checkForUpdate failed:", e.message);
|
||||
return { code: -1, msg: `Failed to check update: ${e.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
const downloadUpdate = async () => {
|
||||
try {
|
||||
const result = await autoUpdater.downloadUpdate();
|
||||
Log.info("[Updater] download result:", JSON.stringify(result));
|
||||
return { code: 0, msg: "success" };
|
||||
} catch (e: any) {
|
||||
Log.error("[Updater] downloadUpdate failed:", e.message);
|
||||
return { code: -1, msg: `Failed to download update: ${e.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
const quitAndInstall = async () => {
|
||||
try {
|
||||
Log.info("[Updater] Starting quitAndInstall process...");
|
||||
(global as any).__isUpdating = true;
|
||||
Log.info("[Updater] Set isUpdating flag");
|
||||
|
||||
Log.info("[Updater] Cleaning up child processes (attempt 1)...");
|
||||
await ProcessCleanupManager.cleanupAllProcesses();
|
||||
|
||||
Log.info("[Updater] Cleaning up child processes (attempt 2)...");
|
||||
await ProcessCleanupManager.cleanupAllProcesses();
|
||||
|
||||
Log.info("[Updater] Child processes cleaned up");
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
Log.info("[Updater] Starting update installation...", {
|
||||
downloadedSetupPath,
|
||||
differentialDownloadDisabled: autoUpdater.disableDifferentialDownload,
|
||||
});
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
|
||||
return { code: 0, msg: "success" };
|
||||
} catch (e: any) {
|
||||
Log.error("[Updater] quitAndInstall failed:", e.message);
|
||||
return { code: -1, msg: `Failed to quit and install: ${e.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
checkForUpdate,
|
||||
downloadUpdate,
|
||||
quitAndInstall,
|
||||
getLatestStatus,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import updaterIndex, { setUpdaterWindow } from "./index";
|
||||
import { ipcMain } from "electron";
|
||||
import ConfigMain from "../config/main";
|
||||
|
||||
ipcMain.handle("updater:getCheckAtLaunch", async () => {
|
||||
return ConfigMain.get("updaterCheckAtLaunch", "yes");
|
||||
});
|
||||
|
||||
ipcMain.handle("updater:setCheckAtLaunch", async (event, value) => {
|
||||
return ConfigMain.set("updaterCheckAtLaunch", value);
|
||||
});
|
||||
|
||||
ipcMain.handle("updater:getLatestStatus", async () => {
|
||||
return updaterIndex.getLatestStatus();
|
||||
});
|
||||
|
||||
ipcMain.handle("updater:checkForUpdate", async () => {
|
||||
return await updaterIndex.checkForUpdate();
|
||||
});
|
||||
|
||||
ipcMain.handle("updater:downloadUpdate", async () => {
|
||||
return await updaterIndex.downloadUpdate();
|
||||
});
|
||||
|
||||
ipcMain.handle("updater:quitAndInstall", async () => {
|
||||
updaterIndex.quitAndInstall();
|
||||
});
|
||||
|
||||
export const UpdaterMain = {
|
||||
...updaterIndex,
|
||||
setUpdaterWindow,
|
||||
};
|
||||
|
||||
export default UpdaterMain;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
const getCheckAtLaunch = async (): Promise<"yes" | "no"> => {
|
||||
return ipcRenderer.invoke("updater:getCheckAtLaunch");
|
||||
};
|
||||
|
||||
const setCheckAtLaunch = async (value: "yes" | "no"): Promise<void> => {
|
||||
return ipcRenderer.invoke("updater:setCheckAtLaunch", value);
|
||||
};
|
||||
|
||||
const getLatestStatus = async () => {
|
||||
return ipcRenderer.invoke("updater:getLatestStatus");
|
||||
};
|
||||
|
||||
const checkForUpdate = async () => {
|
||||
return ipcRenderer.invoke("updater:checkForUpdate");
|
||||
};
|
||||
|
||||
const downloadUpdate = async () => {
|
||||
return ipcRenderer.invoke("updater:downloadUpdate");
|
||||
};
|
||||
|
||||
const quitAndInstall = async () => {
|
||||
return ipcRenderer.invoke("updater:quitAndInstall");
|
||||
};
|
||||
|
||||
const onUpdaterStatus = (callback: (data: any) => void) => {
|
||||
const handler = (_event: any, data: any) => callback(data);
|
||||
ipcRenderer.on("updater:status", handler);
|
||||
return () => {
|
||||
ipcRenderer.removeListener("updater:status", handler);
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
checkForUpdate,
|
||||
downloadUpdate,
|
||||
quitAndInstall,
|
||||
getCheckAtLaunch,
|
||||
setCheckAtLaunch,
|
||||
getLatestStatus,
|
||||
onUpdaterStatus,
|
||||
};
|
||||
Reference in New Issue
Block a user