mirror of
https://github.com/xxnuo/MTranServer.git
synced 2026-09-03 06:35:20 +08:00
feat(desktop): updater and autostart
This commit is contained in:
@@ -18,8 +18,10 @@ let locale = 'en';
|
||||
let messages = getMessages(locale);
|
||||
let tray = null;
|
||||
const repoUrl = 'https://github.com/xxnuo/MTranServer';
|
||||
const releasesUrl = 'https://github.com/xxnuo/MTranServer/releases';
|
||||
let portCheckPromise = null;
|
||||
let desktopLogPath = null;
|
||||
let newVersionAvailable = null;
|
||||
|
||||
function getLocalHost(host) {
|
||||
if (!host || host === '0.0.0.0') return '127.0.0.1';
|
||||
@@ -38,6 +40,38 @@ function getSettingsUrl(server) {
|
||||
return `http://${getLocalHost(server.host)}:${server.port}/ui/settings`;
|
||||
}
|
||||
|
||||
function compareVersions(current, latest) {
|
||||
const parseCurrent = current.replace(/^v/, '').split('.').map(Number);
|
||||
const parseLatest = latest.replace(/^v/, '').split('.').map(Number);
|
||||
for (let i = 0; i < Math.max(parseCurrent.length, parseLatest.length); i++) {
|
||||
const a = parseCurrent[i] || 0;
|
||||
const b = parseLatest[i] || 0;
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function checkForUpdate() {
|
||||
try {
|
||||
const response = await fetch(releasesUrl, {
|
||||
headers: { 'Accept': 'text/html' },
|
||||
redirect: 'follow',
|
||||
});
|
||||
if (!response.ok) return;
|
||||
const html = await response.text();
|
||||
const match = html.match(/\/xxnuo\/MTranServer\/releases\/tag\/v?([\d.]+)/);
|
||||
if (!match) return;
|
||||
const latestVersion = match[1];
|
||||
const currentVersion = app.getVersion();
|
||||
if (compareVersions(currentVersion, latestVersion) < 0) {
|
||||
newVersionAvailable = latestVersion;
|
||||
updateTray();
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel() {
|
||||
const status = getServerStatus();
|
||||
if (status === 'running') {
|
||||
@@ -76,6 +110,8 @@ function updateTray() {
|
||||
messages,
|
||||
statusLabel: getStatusLabel(),
|
||||
versionLabel: app.getVersion(),
|
||||
newVersionLabel: newVersionAvailable,
|
||||
autoStartEnabled: app.getLoginItemSettings().openAtLogin,
|
||||
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
|
||||
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
|
||||
onOpenRepo: () => shell.openExternal(repoUrl),
|
||||
@@ -83,10 +119,18 @@ function updateTray() {
|
||||
onRestart: restartServer,
|
||||
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
|
||||
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
|
||||
onToggleAutoStart: toggleAutoStart,
|
||||
onOpenReleasePage: () => shell.openExternal(releasesUrl),
|
||||
onQuit: quitApp
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAutoStart() {
|
||||
const current = app.getLoginItemSettings().openAtLogin;
|
||||
app.setLoginItemSettings({ openAtLogin: !current });
|
||||
updateTray();
|
||||
}
|
||||
|
||||
async function logDesktop(message, error) {
|
||||
if (!desktopLogPath) return;
|
||||
const timestamp = new Date().toISOString();
|
||||
@@ -244,6 +288,8 @@ export async function startDesktop() {
|
||||
messages,
|
||||
statusLabel: getStatusLabel(),
|
||||
versionLabel: app.getVersion(),
|
||||
newVersionLabel: newVersionAvailable,
|
||||
autoStartEnabled: app.getLoginItemSettings().openAtLogin,
|
||||
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
|
||||
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
|
||||
onOpenRepo: () => shell.openExternal(repoUrl),
|
||||
@@ -251,6 +297,8 @@ export async function startDesktop() {
|
||||
onRestart: restartServer,
|
||||
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
|
||||
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
|
||||
onToggleAutoStart: toggleAutoStart,
|
||||
onOpenReleasePage: () => shell.openExternal(releasesUrl),
|
||||
onQuit: quitApp
|
||||
});
|
||||
|
||||
@@ -260,6 +308,8 @@ export async function startDesktop() {
|
||||
return;
|
||||
}
|
||||
|
||||
checkForUpdate();
|
||||
|
||||
const getConfigResponse = async () => buildDesktopResponse();
|
||||
const applyConfig = async (config) => {
|
||||
desktopConfig = await saveDesktopConfig(config);
|
||||
|
||||
@@ -76,28 +76,78 @@ function normalizeConfig(input) {
|
||||
};
|
||||
}
|
||||
|
||||
function getDiffConfig(config) {
|
||||
const defaults = getDefaultDesktopConfig();
|
||||
const diff = {};
|
||||
if (config.locale !== defaults.locale) {
|
||||
diff.locale = config.locale;
|
||||
}
|
||||
const serverDiff = {};
|
||||
const serverKeys = Object.keys(defaults.server);
|
||||
for (const key of serverKeys) {
|
||||
if (config.server[key] !== defaults.server[key]) {
|
||||
serverDiff[key] = config.server[key];
|
||||
}
|
||||
}
|
||||
if (Object.keys(serverDiff).length > 0) {
|
||||
diff.server = serverDiff;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
function isConfigDefault(config) {
|
||||
const diff = getDiffConfig(config);
|
||||
return Object.keys(diff).length === 0;
|
||||
}
|
||||
|
||||
export async function loadDesktopConfig() {
|
||||
try {
|
||||
const raw = await fsPromises.readFile(desktopConfigPath, 'utf8');
|
||||
const parsed = YAML.parse(raw);
|
||||
return normalizeConfig(parsed);
|
||||
} catch (error) {
|
||||
const defaults = getDefaultDesktopConfig();
|
||||
await saveDesktopConfig(defaults);
|
||||
return defaults;
|
||||
const normalized = normalizeConfig(parsed);
|
||||
const diff = getDiffConfig(normalized);
|
||||
if (Object.keys(diff).length === 0) {
|
||||
try {
|
||||
await fsPromises.unlink(desktopConfigPath);
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
const currentDiff = getDiffConfig(normalizeConfig(parsed));
|
||||
const parsedKeys = Object.keys(parsed?.server || {});
|
||||
const defaultKeys = Object.keys(getDefaultDesktopConfig().server);
|
||||
const hasRemovedKeys = parsedKeys.some(k => !defaultKeys.includes(k));
|
||||
if (hasRemovedKeys) {
|
||||
await fsPromises.mkdir(serverConfigDir, { recursive: true });
|
||||
const data = YAML.stringify(diff);
|
||||
await fsPromises.writeFile(desktopConfigPath, data, 'utf8');
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
} catch {
|
||||
return getDefaultDesktopConfig();
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveDesktopConfig(config) {
|
||||
const normalized = normalizeConfig(config);
|
||||
await fsPromises.mkdir(serverConfigDir, { recursive: true });
|
||||
const data = YAML.stringify(normalized);
|
||||
await fsPromises.writeFile(desktopConfigPath, data, 'utf8');
|
||||
const diff = getDiffConfig(normalized);
|
||||
if (Object.keys(diff).length === 0) {
|
||||
try {
|
||||
await fsPromises.unlink(desktopConfigPath);
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
await fsPromises.mkdir(serverConfigDir, { recursive: true });
|
||||
const data = YAML.stringify(diff);
|
||||
await fsPromises.writeFile(desktopConfigPath, data, 'utf8');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export async function resetDesktopConfig() {
|
||||
const defaults = getDefaultDesktopConfig();
|
||||
await saveDesktopConfig(defaults);
|
||||
return defaults;
|
||||
try {
|
||||
await fsPromises.unlink(desktopConfigPath);
|
||||
} catch {
|
||||
}
|
||||
return getDefaultDesktopConfig();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ const messages = {
|
||||
trayOpenModels: 'Open Model Folder',
|
||||
trayOpenConfig: 'Open Config Folder',
|
||||
trayVersion: 'Version',
|
||||
trayNewVersion: 'New version available: v{version}',
|
||||
trayAutoStart: 'Start at Login',
|
||||
trayQuit: 'Quit',
|
||||
appName: 'MTranServer',
|
||||
portInUseTitle: 'Port is in use',
|
||||
@@ -41,6 +43,8 @@ const messages = {
|
||||
trayOpenModels: '打开模型目录',
|
||||
trayOpenConfig: '打开配置目录',
|
||||
trayVersion: '版本号',
|
||||
trayNewVersion: '发现新版本: v{version}',
|
||||
trayAutoStart: '开机启动',
|
||||
trayQuit: '退出程序',
|
||||
appName: 'MTranServer',
|
||||
portInUseTitle: '端口被占用',
|
||||
@@ -67,6 +71,8 @@ const messages = {
|
||||
trayOpenModels: 'モデルフォルダを開く',
|
||||
trayOpenConfig: '設定フォルダを開く',
|
||||
trayVersion: 'バージョン',
|
||||
trayNewVersion: '新しいバージョンがあります: v{version}',
|
||||
trayAutoStart: 'ログイン時に起動',
|
||||
trayQuit: '終了',
|
||||
appName: 'MTranServer',
|
||||
portInUseTitle: 'ポートが使用中です',
|
||||
|
||||
@@ -8,6 +8,8 @@ export function createTray({
|
||||
messages,
|
||||
statusLabel,
|
||||
versionLabel,
|
||||
newVersionLabel,
|
||||
autoStartEnabled,
|
||||
onOpenBrowserUi,
|
||||
onOpenBrowserDocs,
|
||||
onOpenRepo,
|
||||
@@ -15,6 +17,8 @@ export function createTray({
|
||||
onRestart,
|
||||
onOpenModels,
|
||||
onOpenConfig,
|
||||
onToggleAutoStart,
|
||||
onOpenReleasePage,
|
||||
onQuit
|
||||
}) {
|
||||
if (tray) return tray;
|
||||
@@ -25,6 +29,8 @@ export function createTray({
|
||||
messages,
|
||||
statusLabel,
|
||||
versionLabel,
|
||||
newVersionLabel,
|
||||
autoStartEnabled,
|
||||
onOpenBrowserUi,
|
||||
onOpenBrowserDocs,
|
||||
onOpenRepo,
|
||||
@@ -32,6 +38,8 @@ export function createTray({
|
||||
onRestart,
|
||||
onOpenModels,
|
||||
onOpenConfig,
|
||||
onToggleAutoStart,
|
||||
onOpenReleasePage,
|
||||
onQuit
|
||||
});
|
||||
return tray;
|
||||
@@ -41,6 +49,8 @@ export function updateTrayMenu({
|
||||
messages,
|
||||
statusLabel,
|
||||
versionLabel,
|
||||
newVersionLabel,
|
||||
autoStartEnabled,
|
||||
onOpenBrowserUi,
|
||||
onOpenBrowserDocs,
|
||||
onOpenRepo,
|
||||
@@ -48,10 +58,12 @@ export function updateTrayMenu({
|
||||
onRestart,
|
||||
onOpenModels,
|
||||
onOpenConfig,
|
||||
onToggleAutoStart,
|
||||
onOpenReleasePage,
|
||||
onQuit
|
||||
}) {
|
||||
if (!tray) return;
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
const menuItems = [
|
||||
{
|
||||
label: messages.trayOpenUi,
|
||||
click: onOpenBrowserUi
|
||||
@@ -76,6 +88,12 @@ export function updateTrayMenu({
|
||||
{ label: messages.trayRestart, click: onRestart }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: messages.trayAutoStart,
|
||||
type: 'checkbox',
|
||||
checked: autoStartEnabled,
|
||||
click: onToggleAutoStart
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: messages.trayData,
|
||||
@@ -87,11 +105,18 @@ export function updateTrayMenu({
|
||||
{
|
||||
label: `${messages.trayVersion}: ${versionLabel}`,
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
label: messages.trayQuit,
|
||||
click: onQuit
|
||||
}
|
||||
]);
|
||||
];
|
||||
if (newVersionLabel) {
|
||||
menuItems.push({
|
||||
label: messages.trayNewVersion.replace('{version}', newVersionLabel),
|
||||
click: onOpenReleasePage
|
||||
});
|
||||
}
|
||||
menuItems.push({
|
||||
label: messages.trayQuit,
|
||||
click: onQuit
|
||||
});
|
||||
const contextMenu = Menu.buildFromTemplate(menuItems);
|
||||
tray.setContextMenu(contextMenu);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user