first-update
This commit is contained in:
21
easy-dataset-main/electron/modules/cache.js
Normal file
21
easy-dataset-main/electron/modules/cache.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { clearLogs } = require('./logger');
|
||||
const { clearDatabaseCache } = require('./database');
|
||||
|
||||
/**
|
||||
* 清除缓存函数 - 清理logs和local-db目录
|
||||
* @param {Object} app Electron app 对象
|
||||
* @returns {Promise<boolean>} 操作是否成功
|
||||
*/
|
||||
async function clearCache(app) {
|
||||
// 清理日志目录
|
||||
await clearLogs(app);
|
||||
|
||||
// 清理数据库缓存
|
||||
await clearDatabaseCache(app);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
clearCache
|
||||
};
|
||||
147
easy-dataset-main/electron/modules/database.js
Normal file
147
easy-dataset-main/electron/modules/database.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { dialog } = require('electron');
|
||||
const { updateDatabase } = require('./db-updater');
|
||||
|
||||
/**
|
||||
* 清除数据库缓存
|
||||
* @param {Object} app Electron app 对象
|
||||
* @returns {Promise<boolean>} 操作是否成功
|
||||
*/
|
||||
async function clearDatabaseCache(app) {
|
||||
// 清理local-db目录,保留db.sqlite文件
|
||||
const localDbDir = path.join(app.getPath('userData'), 'local-db');
|
||||
if (fs.existsSync(localDbDir)) {
|
||||
// 读取目录下所有文件
|
||||
const files = await fs.promises.readdir(localDbDir);
|
||||
// 删除除了db.sqlite之外的所有文件
|
||||
for (const file of files) {
|
||||
if (file !== 'db.sqlite') {
|
||||
const filePath = path.join(localDbDir, file);
|
||||
const stat = await fs.promises.stat(filePath);
|
||||
if (stat.isFile()) {
|
||||
await fs.promises.unlink(filePath);
|
||||
global.appLog(`已删除数据库缓存文件: ${filePath}`);
|
||||
} else if (stat.isDirectory()) {
|
||||
// 如果是目录,可能需要递归删除,根据需求决定
|
||||
global.appLog(`跳过目录: ${filePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
* @param {Object} app Electron app 对象
|
||||
* @returns {Promise<Object>} 数据库配置信息
|
||||
*/
|
||||
async function initializeDatabase(app) {
|
||||
try {
|
||||
// 设置数据库路径
|
||||
const userDataPath = app.getPath('userData');
|
||||
const dataDir = path.join(userDataPath, 'local-db');
|
||||
const dbFilePath = path.join(dataDir, 'db.sqlite');
|
||||
const dbJSONPath = path.join(dataDir, 'db.json');
|
||||
fs.writeFileSync(path.join(process.resourcesPath, 'root-path.txt'), dataDir);
|
||||
|
||||
// 确保数据目录存在
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
console.log(`数据目录已创建: ${dataDir}`);
|
||||
}
|
||||
|
||||
// 设置数据库连接字符串 (Prisma 格式)
|
||||
const dbConnectionString = `file:${dbFilePath}`;
|
||||
process.env.DATABASE_URL = dbConnectionString;
|
||||
|
||||
// 仅在开发环境记录日志
|
||||
const logs = {
|
||||
userDataPath,
|
||||
dataDir,
|
||||
dbFilePath,
|
||||
dbConnectionString,
|
||||
dbExists: fs.existsSync(dbFilePath)
|
||||
};
|
||||
global.appLog(`数据库配置: ${JSON.stringify(logs)}`);
|
||||
|
||||
if (!fs.existsSync(dbFilePath)) {
|
||||
global.appLog('数据库文件不存在,正在初始化...');
|
||||
|
||||
try {
|
||||
const resourcePath =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? path.join(__dirname, '../..', 'prisma', 'template.sqlite')
|
||||
: path.join(process.resourcesPath, 'prisma', 'template.sqlite');
|
||||
|
||||
const resourceJSONPath =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? path.join(__dirname, '../..', 'prisma', 'sql.json')
|
||||
: path.join(process.resourcesPath, 'prisma', 'sql.json');
|
||||
|
||||
global.appLog(`resourcePath: ${resourcePath}`);
|
||||
|
||||
if (fs.existsSync(resourcePath)) {
|
||||
fs.copyFileSync(resourcePath, dbFilePath);
|
||||
global.appLog(`数据库已从模板初始化: ${dbFilePath}`);
|
||||
}
|
||||
|
||||
if (fs.existsSync(resourceJSONPath)) {
|
||||
fs.copyFileSync(resourceJSONPath, dbJSONPath);
|
||||
global.appLog(`数据库SQL配置已初始化: ${dbJSONPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('数据库初始化失败:', error);
|
||||
dialog.showErrorBox('数据库初始化失败', `应用无法初始化数据库,可能需要重新安装。\n错误详情: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// 数据库文件存在,检查是否需要更新
|
||||
global.appLog('检查数据库是否需要更新...');
|
||||
try {
|
||||
const resourcesPath =
|
||||
process.env.NODE_ENV === 'development' ? path.join(__dirname, '../..') : process.resourcesPath;
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
// 更新数据库
|
||||
const result = await updateDatabase(userDataPath, resourcesPath, isDev, global.appLog);
|
||||
|
||||
if (result.updated) {
|
||||
global.appLog(`数据库更新成功: ${result.message}`);
|
||||
global.appLog(`执行的版本: ${result.executedVersions.join(', ')}`);
|
||||
} else {
|
||||
global.appLog(`数据库无需更新: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('数据库更新失败:', error);
|
||||
global.appLog(`数据库更新失败: ${error.message}`, 'error');
|
||||
|
||||
// 非致命错误,只提示但不阻止应用启动
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
title: '数据库更新警告',
|
||||
message: '数据库更新过程中出现错误,部分功能可能受影响。',
|
||||
detail: `错误详情: ${error.message}\n\n您可以继续使用应用,但如果遇到问题,请重新安装应用。`,
|
||||
buttons: ['继续']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
userDataPath,
|
||||
dataDir,
|
||||
dbFilePath,
|
||||
dbConnectionString
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('初始化数据库时发生错误:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
clearDatabaseCache,
|
||||
initializeDatabase
|
||||
};
|
||||
179
easy-dataset-main/electron/modules/db-updater.js
Normal file
179
easy-dataset-main/electron/modules/db-updater.js
Normal file
@@ -0,0 +1,179 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
/**
|
||||
* 执行SQL命令
|
||||
* @param {string} dbUrl 数据库连接 URL
|
||||
* @param {string} sql SQL命令
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function executeSql(dbUrl, sql) {
|
||||
// 允许多条SQL语句分开执行,支持分号和空行分隔
|
||||
const statements = sql
|
||||
.split(';')
|
||||
.map(stmt => stmt.trim())
|
||||
.filter(stmt => stmt.length > 0);
|
||||
|
||||
if (statements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置环境变量
|
||||
process.env.DATABASE_URL = dbUrl;
|
||||
|
||||
// 创建Prisma实例
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
try {
|
||||
// 执行每条SQL语句
|
||||
for (const statement of statements) {
|
||||
await prisma.$executeRawUnsafe(statement);
|
||||
}
|
||||
} finally {
|
||||
// 关闭连接
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地和应用的SQL配置文件
|
||||
* @param {string} userDataPath 用户数据目录
|
||||
* @param {string} resourcesPath 应用资源目录
|
||||
* @param {boolean} isDev 是否开发环境
|
||||
* @returns {Promise<{userSqlConfig: Array, appSqlConfig: Array}>}
|
||||
*/
|
||||
async function getSqlConfigs(userDataPath, resourcesPath, isDev, logger = console.log) {
|
||||
// 用户SQL配置文件路径
|
||||
const userSqlPath = path.join(userDataPath, 'sql.json');
|
||||
|
||||
// 应用SQL配置文件路径
|
||||
const appSqlPath = isDev
|
||||
? path.join(__dirname, '..', 'prisma', 'sql.json')
|
||||
: path.join(resourcesPath, 'prisma', 'sql.json');
|
||||
|
||||
let userSqlConfig = [];
|
||||
let appSqlConfig = [];
|
||||
|
||||
// 读取应用SQL配置
|
||||
try {
|
||||
if (fs.existsSync(appSqlPath)) {
|
||||
const appSqlContent = fs.readFileSync(appSqlPath, 'utf8');
|
||||
appSqlConfig = JSON.parse(appSqlContent);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`读取应用SQL配置文件失败: ${error.message}`);
|
||||
}
|
||||
|
||||
// 读取用户SQL配置(如果存在)
|
||||
try {
|
||||
if (fs.existsSync(userSqlPath)) {
|
||||
const userSqlContent = fs.readFileSync(userSqlPath, 'utf8');
|
||||
userSqlConfig = JSON.parse(userSqlContent);
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果用户SQL配置不存在或无法解析,使用空数组
|
||||
userSqlConfig = [];
|
||||
}
|
||||
|
||||
logger(appSqlPath);
|
||||
// logger(JSON.stringify(appSqlConfig, null, 2));
|
||||
logger(userSqlPath);
|
||||
// logger(JSON.stringify(userSqlConfig, null, 2));
|
||||
|
||||
return { userSqlConfig, appSqlConfig };
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户SQL配置文件
|
||||
* @param {string} userDataPath 用户数据目录
|
||||
* @param {Array} sqlConfig 新的SQL配置
|
||||
*/
|
||||
function updateUserSqlConfig(userDataPath, sqlConfig) {
|
||||
const userSqlPath = path.join(userDataPath, 'sql.json');
|
||||
fs.writeFileSync(userSqlPath, JSON.stringify(sqlConfig, null, 4), 'utf8');
|
||||
}
|
||||
|
||||
// 不再需要版本比较功能
|
||||
|
||||
/**
|
||||
* 获取需要执行的SQL命令
|
||||
* @param {Array} userSqlConfig 用户SQL配置
|
||||
* @param {Array} appSqlConfig 应用SQL配置
|
||||
* @returns {Array} 需要执行的SQL命令
|
||||
*/
|
||||
function getSqlsToExecute(userSqlConfig, appSqlConfig) {
|
||||
// 创建用户已执行的SQL集合 (使用 version + sql 的组合作为唯一标识)
|
||||
const userExecutedSqlSet = new Set();
|
||||
userSqlConfig.forEach(item => {
|
||||
const key = `${item.version}:${item.sql}`;
|
||||
userExecutedSqlSet.add(key);
|
||||
});
|
||||
|
||||
// 过滤出用户需要执行的SQL (即应用SQL配置中存在但用户尚未执行的SQL)
|
||||
return appSqlConfig.filter(item => {
|
||||
const key = `${item.version}:${item.sql}`;
|
||||
return !userExecutedSqlSet.has(key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据库
|
||||
* @param {string} userDataPath 用户数据目录
|
||||
* @param {string} resourcesPath 应用资源目录
|
||||
* @param {boolean} isDev 是否开发环境
|
||||
* @param {function} logger 日志函数
|
||||
*/
|
||||
async function updateDatabase(userDataPath, resourcesPath, isDev, logger = console.log) {
|
||||
const dbPath = path.join(userDataPath, 'local-db', 'db.sqlite');
|
||||
|
||||
try {
|
||||
// 获取SQL配置
|
||||
const { userSqlConfig, appSqlConfig } = await getSqlConfigs(userDataPath, resourcesPath, isDev, logger);
|
||||
|
||||
// 获取需要执行的SQL
|
||||
const sqlsToExecute = getSqlsToExecute(userSqlConfig, appSqlConfig);
|
||||
|
||||
if (sqlsToExecute.length === 0) {
|
||||
logger('数据库已是最新版本,无需更新');
|
||||
return { updated: false, message: '数据库已是最新版本' };
|
||||
}
|
||||
|
||||
// 设置数据库URL
|
||||
const dbUrl = `file:${dbPath}`;
|
||||
|
||||
// 执行SQL更新
|
||||
logger(`发现 ${sqlsToExecute.length} 个数据库更新,开始执行...`);
|
||||
for (const item of sqlsToExecute) {
|
||||
try {
|
||||
logger(`执行版本 ${item.version} 的SQL更新: ${item.sql.substring(0, 100)}...`);
|
||||
await executeSql(dbUrl, item.sql);
|
||||
// 添加到用户SQL配置
|
||||
userSqlConfig.push(item);
|
||||
} catch (error) {
|
||||
logger(`执行版本 ${item.version} 的SQL更新失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户SQL配置文件
|
||||
updateUserSqlConfig(userDataPath, userSqlConfig);
|
||||
|
||||
logger('数据库更新完成');
|
||||
return {
|
||||
updated: true,
|
||||
message: `成功执行了 ${sqlsToExecute.length} 个数据库更新`,
|
||||
executedVersions: sqlsToExecute.map(item => item.version)
|
||||
};
|
||||
} catch (error) {
|
||||
logger(`数据库更新失败: ${error.message}`);
|
||||
return { updated: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateDatabase,
|
||||
executeSql,
|
||||
getSqlConfigs,
|
||||
updateUserSqlConfig,
|
||||
getSqlsToExecute
|
||||
};
|
||||
33
easy-dataset-main/electron/modules/ipc-handlers.js
Normal file
33
easy-dataset-main/electron/modules/ipc-handlers.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { ipcMain } = require('electron');
|
||||
const { checkUpdate, downloadUpdate, installUpdate } = require('./updater');
|
||||
|
||||
/**
|
||||
* 设置 IPC 处理程序
|
||||
* @param {Object} app Electron app 对象
|
||||
* @param {boolean} isDev 是否为开发环境
|
||||
*/
|
||||
function setupIpcHandlers(app, isDev) {
|
||||
// 获取用户数据路径
|
||||
ipcMain.on('get-user-data-path', event => {
|
||||
event.returnValue = app.getPath('userData');
|
||||
});
|
||||
|
||||
// 检查更新
|
||||
ipcMain.handle('check-update', async () => {
|
||||
return await checkUpdate(isDev);
|
||||
});
|
||||
|
||||
// 下载更新
|
||||
ipcMain.handle('download-update', async () => {
|
||||
return await downloadUpdate();
|
||||
});
|
||||
|
||||
// 安装更新
|
||||
ipcMain.handle('install-update', () => {
|
||||
return installUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupIpcHandlers
|
||||
};
|
||||
84
easy-dataset-main/electron/modules/logger.js
Normal file
84
easy-dataset-main/electron/modules/logger.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 设置应用日志系统
|
||||
* @param {Object} app Electron app 对象
|
||||
* @returns {string} 日志文件路径
|
||||
*/
|
||||
function setupLogging(app) {
|
||||
const logDir = path.join(app.getPath('userData'), 'logs');
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
const logFilePath = path.join(logDir, `app-${new Date().toISOString().slice(0, 10)}.log`);
|
||||
|
||||
// 创建自定义日志函数
|
||||
global.appLog = (message, level = 'info') => {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
||||
|
||||
// 同时输出到控制台和日志文件
|
||||
console.log(message);
|
||||
fs.appendFileSync(logFilePath, logEntry);
|
||||
};
|
||||
|
||||
// 捕获全局未处理异常并记录
|
||||
process.on('uncaughtException', error => {
|
||||
global.appLog(`未捕获的异常: ${error.stack || error}`, 'error');
|
||||
});
|
||||
|
||||
return logFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 IPC 日志处理程序
|
||||
* @param {Object} ipcMain IPC 主进程对象
|
||||
* @param {Object} app Electron app 对象
|
||||
* @param {boolean} isDev 是否为开发环境
|
||||
*/
|
||||
function setupIpcLogging(ipcMain, app, isDev) {
|
||||
ipcMain.on('log', (event, { level, message }) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
||||
|
||||
// 只在客户端环境下写入文件
|
||||
if (!isDev || true) {
|
||||
const logsDir = path.join(app.getPath('userData'), 'logs');
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
}
|
||||
const logFile = path.join(logsDir, `${new Date().toISOString().split('T')[0]}.log`);
|
||||
fs.appendFileSync(logFile, logEntry);
|
||||
}
|
||||
|
||||
// 同时输出到控制台
|
||||
console[level](message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理日志文件
|
||||
* @param {Object} app Electron app 对象
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function clearLogs(app) {
|
||||
const logsDir = path.join(app.getPath('userData'), 'logs');
|
||||
if (fs.existsSync(logsDir)) {
|
||||
// 读取目录下所有文件
|
||||
const files = await fs.promises.readdir(logsDir);
|
||||
// 删除所有文件
|
||||
for (const file of files) {
|
||||
const filePath = path.join(logsDir, file);
|
||||
await fs.promises.unlink(filePath);
|
||||
global.appLog(`已删除日志文件: ${filePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupLogging,
|
||||
setupIpcLogging,
|
||||
clearLogs
|
||||
};
|
||||
136
easy-dataset-main/electron/modules/menu.js
Normal file
136
easy-dataset-main/electron/modules/menu.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const { Menu, dialog, shell, app } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const { getAppVersion } = require('../util');
|
||||
|
||||
/**
|
||||
* 创建应用菜单
|
||||
* @param {BrowserWindow} mainWindow 主窗口
|
||||
* @param {Function} clearCache 清除缓存函数
|
||||
*/
|
||||
function createMenu(mainWindow, clearCache) {
|
||||
const template = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [{ role: 'quit', label: 'Quit' }]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ role: 'undo', label: 'Undo' },
|
||||
{ role: 'redo', label: 'Redo' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'cut', label: 'Cut' },
|
||||
{ role: 'copy', label: 'Copy' },
|
||||
{ role: 'paste', label: 'Paste' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{ role: 'reload', label: 'Refresh' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'resetzoom', label: 'Reset Zoom' },
|
||||
{ role: 'zoomin', label: 'Zoom In' },
|
||||
{ role: 'zoomout', label: 'Zoom Out' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'togglefullscreen', label: 'Fullscreen' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About',
|
||||
click: () => {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
title: 'About Easy Dataset',
|
||||
message: `Easy Dataset v${getAppVersion()}`,
|
||||
detail: 'An application for creating fine-tuning datasets for large models.',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Visit GitHub',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/ConardLi/easy-dataset');
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'More',
|
||||
submenu: [
|
||||
{ role: 'toggledevtools', label: 'Developer Tools' },
|
||||
{
|
||||
label: 'Open Logs Directory',
|
||||
click: () => {
|
||||
const logsDir = path.join(app.getPath('userData'), 'logs');
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
}
|
||||
shell.openPath(logsDir);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Open Data Directory',
|
||||
click: () => {
|
||||
const dataDir = path.join(app.getPath('userData'), 'local-db');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
shell.openPath(dataDir);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Open Data Directory (History)',
|
||||
click: () => {
|
||||
const dataDir = path.join(os.homedir(), '.easy-dataset-db');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
shell.openPath(dataDir);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Clear Cache',
|
||||
click: async () => {
|
||||
try {
|
||||
const response = await dialog.showMessageBox(mainWindow, {
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'Confirm'],
|
||||
defaultId: 1,
|
||||
title: 'Clear Cache',
|
||||
message: 'Are you sure you want to clear the cache?',
|
||||
detail:
|
||||
'This will delete all files in the logs directory and local database cache files (excluding main database files).'
|
||||
});
|
||||
|
||||
if (response.response === 1) {
|
||||
// User clicked confirm
|
||||
await clearCache();
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Cleared Successfully',
|
||||
message: 'Cache has been cleared successfully'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
global.appLog(`Failed to clear cache: ${error.message}`, 'error');
|
||||
dialog.showErrorBox('Failed to clear cache', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createMenu
|
||||
};
|
||||
118
easy-dataset-main/electron/modules/server.js
Normal file
118
easy-dataset-main/electron/modules/server.js
Normal file
@@ -0,0 +1,118 @@
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { dialog } = require('electron');
|
||||
|
||||
/**
|
||||
* 检查端口是否被占用
|
||||
* @param {number} port 端口号
|
||||
* @returns {Promise<boolean>} 端口是否被占用
|
||||
*/
|
||||
function checkPort(port) {
|
||||
return new Promise(resolve => {
|
||||
const server = http.createServer();
|
||||
server.once('error', () => {
|
||||
resolve(true); // 端口被占用
|
||||
});
|
||||
server.once('listening', () => {
|
||||
server.close();
|
||||
resolve(false); // 端口未被占用
|
||||
});
|
||||
server.listen(port);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 Next.js 服务
|
||||
* @param {number} port 端口号
|
||||
* @param {Object} app Electron app 对象
|
||||
* @returns {Promise<string>} 服务URL
|
||||
*/
|
||||
async function startNextServer(port, app) {
|
||||
console.log(`Easy Dataset 客户端启动中,当前版本: ${require('../util').getAppVersion()}`);
|
||||
|
||||
// 设置日志文件路径
|
||||
const logDir = path.join(app.getPath('userData'), 'logs');
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
const logFile = path.join(logDir, `nextjs-${new Date().toISOString().replace(/:/g, '-')}.log`);
|
||||
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
||||
|
||||
// 重定向 console.log 和 console.error
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
console.log = function () {
|
||||
const args = Array.from(arguments);
|
||||
const logMessage = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg)).join(' ');
|
||||
|
||||
logStream.write(`[${new Date().toISOString()}] [LOG] ${logMessage}\n`);
|
||||
originalConsoleLog.apply(console, args);
|
||||
};
|
||||
|
||||
console.error = function () {
|
||||
const args = Array.from(arguments);
|
||||
const logMessage = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg)).join(' ');
|
||||
|
||||
logStream.write(`[${new Date().toISOString()}] [ERROR] ${logMessage}\n`);
|
||||
originalConsoleError.apply(console, args);
|
||||
};
|
||||
|
||||
// 检查端口是否被占用
|
||||
const isPortBusy = await checkPort(port);
|
||||
if (isPortBusy) {
|
||||
console.log(`端口 ${port} 已被占用,尝试直接连接...`);
|
||||
return `http://localhost:${port}`;
|
||||
}
|
||||
|
||||
console.log(`启动 Next.js 服务,端口: ${port}`);
|
||||
|
||||
try {
|
||||
// 动态导入 Next.js
|
||||
const next = require('next');
|
||||
const nextApp = next({
|
||||
dev: false,
|
||||
dir: path.join(__dirname, '../..'),
|
||||
conf: {
|
||||
// 配置 Next.js 的日志输出
|
||||
onInfo: info => {
|
||||
console.log(`[Next.js Info] ${info}`);
|
||||
},
|
||||
onError: error => {
|
||||
console.error(`[Next.js Error] ${error}`);
|
||||
},
|
||||
onWarn: warn => {
|
||||
console.log(`[Next.js Warning] ${warn}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
const handle = nextApp.getRequestHandler();
|
||||
|
||||
await nextApp.prepare();
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
// 记录请求日志
|
||||
console.log(`[Request] ${req.method} ${req.url}`);
|
||||
handle(req, res);
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
server.listen(port, err => {
|
||||
if (err) throw err;
|
||||
console.log(`服务已启动,正在打开应用...`);
|
||||
resolve(`http://localhost:${port}`);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('启动服务失败:', error);
|
||||
dialog.showErrorBox('启动失败', `无法启动 Next.js 服务: ${error.message}`);
|
||||
app.quit();
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkPort,
|
||||
startNextServer
|
||||
};
|
||||
116
easy-dataset-main/electron/modules/updater.js
Normal file
116
easy-dataset-main/electron/modules/updater.js
Normal file
@@ -0,0 +1,116 @@
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const { getAppVersion } = require('../util');
|
||||
|
||||
/**
|
||||
* 设置自动更新
|
||||
* @param {BrowserWindow} mainWindow 主窗口
|
||||
*/
|
||||
function setupAutoUpdater(mainWindow) {
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.allowDowngrade = false;
|
||||
|
||||
// 检查更新时出错
|
||||
autoUpdater.on('error', error => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('update-error', error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 检查到更新时
|
||||
autoUpdater.on('update-available', info => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('update-available', {
|
||||
version: info.version,
|
||||
releaseDate: info.releaseDate,
|
||||
releaseNotes: info.releaseNotes
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 没有可用更新
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('update-not-available');
|
||||
}
|
||||
});
|
||||
|
||||
// 下载进度
|
||||
autoUpdater.on('download-progress', progressObj => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('download-progress', progressObj);
|
||||
}
|
||||
});
|
||||
|
||||
// 下载完成
|
||||
autoUpdater.on('update-downloaded', info => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('update-downloaded', {
|
||||
version: info.version,
|
||||
releaseDate: info.releaseDate,
|
||||
releaseNotes: info.releaseNotes
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查更新
|
||||
* @param {boolean} isDev 是否为开发环境
|
||||
* @returns {Promise<Object>} 更新信息
|
||||
*/
|
||||
async function checkUpdate(isDev) {
|
||||
try {
|
||||
if (isDev) {
|
||||
// 开发环境下模拟更新检查
|
||||
return {
|
||||
hasUpdate: false,
|
||||
currentVersion: getAppVersion(),
|
||||
message: '开发环境下不检查更新'
|
||||
};
|
||||
}
|
||||
|
||||
// 返回当前版本信息,并开始检查更新
|
||||
const result = await autoUpdater.checkForUpdates();
|
||||
return {
|
||||
checking: true,
|
||||
currentVersion: getAppVersion()
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('检查更新失败:', error);
|
||||
return {
|
||||
hasUpdate: false,
|
||||
currentVersion: getAppVersion(),
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载更新
|
||||
* @returns {Promise<Object>} 下载状态
|
||||
*/
|
||||
async function downloadUpdate() {
|
||||
try {
|
||||
autoUpdater.downloadUpdate();
|
||||
return { downloading: true };
|
||||
} catch (error) {
|
||||
console.error('下载更新失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装更新
|
||||
* @returns {Object} 安装状态
|
||||
*/
|
||||
function installUpdate() {
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
return { installing: true };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupAutoUpdater,
|
||||
checkUpdate,
|
||||
downloadUpdate,
|
||||
installUpdate
|
||||
};
|
||||
113
easy-dataset-main/electron/modules/window-manager.js
Normal file
113
easy-dataset-main/electron/modules/window-manager.js
Normal file
@@ -0,0 +1,113 @@
|
||||
const { BrowserWindow, shell } = require('electron');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const { getAppVersion } = require('../util');
|
||||
|
||||
let mainWindow;
|
||||
|
||||
/**
|
||||
* 创建主窗口
|
||||
* @param {boolean} isDev 是否为开发环境
|
||||
* @param {number} port 服务端口
|
||||
* @returns {BrowserWindow} 创建的主窗口
|
||||
*/
|
||||
function createWindow(isDev, port) {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
show: false,
|
||||
frame: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, '..', 'preload.js')
|
||||
},
|
||||
icon: path.join(__dirname, '../../public/imgs/logo.ico')
|
||||
});
|
||||
|
||||
// 设置窗口标题
|
||||
mainWindow.setTitle(`Easy Dataset v${getAppVersion()}`);
|
||||
const loadingPath = url.format({
|
||||
pathname: path.join(__dirname, '..', 'loading.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
});
|
||||
|
||||
// 加载 loading 页面时使用专门的 preload 脚本
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
mainWindow.loadURL(loadingPath);
|
||||
|
||||
// 处理窗口导航事件,将外部链接在浏览器中打开
|
||||
mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
|
||||
// 解析当前 URL 和导航 URL
|
||||
const parsedUrl = new URL(navigationUrl);
|
||||
const currentHostname = isDev ? 'localhost' : 'localhost';
|
||||
const currentPort = port.toString();
|
||||
|
||||
// 检查是否是外部链接
|
||||
if (parsedUrl.hostname !== currentHostname || (parsedUrl.port !== currentPort && parsedUrl.port !== '')) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(navigationUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// 处理新窗口打开请求,将外部链接在浏览器中打开
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url: navigationUrl }) => {
|
||||
// 解析导航 URL
|
||||
const parsedUrl = new URL(navigationUrl);
|
||||
const currentHostname = isDev ? 'localhost' : 'localhost';
|
||||
const currentPort = port.toString();
|
||||
|
||||
// 检查是否是外部链接
|
||||
if (parsedUrl.hostname !== currentHostname || (parsedUrl.port !== currentPort && parsedUrl.port !== '')) {
|
||||
shell.openExternal(navigationUrl);
|
||||
return { action: 'deny' };
|
||||
}
|
||||
return { action: 'allow' };
|
||||
});
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
mainWindow.maximize();
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载应用URL
|
||||
* @param {string} appUrl 应用URL
|
||||
*/
|
||||
function loadAppUrl(appUrl) {
|
||||
if (mainWindow) {
|
||||
mainWindow.loadURL(appUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在开发环境中打开开发者工具
|
||||
*/
|
||||
function openDevTools() {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主窗口
|
||||
* @returns {BrowserWindow} 主窗口
|
||||
*/
|
||||
function getMainWindow() {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createWindow,
|
||||
loadAppUrl,
|
||||
openDevTools,
|
||||
getMainWindow
|
||||
};
|
||||
Reference in New Issue
Block a user