chore:AI大修改
This commit is contained in:
@@ -9,7 +9,7 @@ import { dinstall, mlsetup } from "./modloader/index.js";
|
||||
import config from "./utils/config.js";
|
||||
import { execPromise } from "./utils/utils.js";
|
||||
import { MessageWS } from "./utils/ws.js";
|
||||
import { debug } from "./utils/logger.js";
|
||||
import { logger } from "./utils/logger.js";
|
||||
|
||||
export class Dex {
|
||||
wsx!: WebSocketServer;
|
||||
@@ -24,136 +24,118 @@ export class Dex {
|
||||
public async Main(buffer: Buffer, dser: boolean) {
|
||||
try {
|
||||
const first = Date.now();
|
||||
const { contain, info } = await this._getinfo(buffer).catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
const { contain, info } = await this._getinfo(buffer);
|
||||
const plat = what_platform(contain);
|
||||
debug(plat);
|
||||
debug(info);
|
||||
logger.debug("Platform detected", plat);
|
||||
logger.debug("Modpack info", info);
|
||||
const mpname = info.name;
|
||||
const unpath = `./instance/${mpname}`;
|
||||
// 解压和下载(并行处理)
|
||||
await Promise.all([
|
||||
this._unzip(buffer, mpname),
|
||||
await platform(plat).downloadfile(info, unpath, this.message),
|
||||
]).catch((e) => {
|
||||
throw new Error(e);
|
||||
}); // 解压和下载
|
||||
platform(plat).downloadfile(info, unpath, this.message)
|
||||
]);
|
||||
this.message.statusChange(); //改变状态
|
||||
await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`)
|
||||
.Main()
|
||||
.catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`).Main();
|
||||
this.message.statusChange(); //改变状态(DeEarth筛选模组完毕)
|
||||
const mlinfo = await platform(plat)
|
||||
.getinfo(info)
|
||||
.catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
const mlinfo = await platform(plat).getinfo(info);
|
||||
if (dser) {
|
||||
await mlsetup(
|
||||
mlinfo.loader,
|
||||
mlinfo.minecraft,
|
||||
mlinfo.loader_version,
|
||||
unpath
|
||||
).catch((e) => {
|
||||
throw new Error(e);
|
||||
}); //安装服务端
|
||||
}
|
||||
if (!dser) {
|
||||
); // 安装服务端
|
||||
} else {
|
||||
dinstall(
|
||||
mlinfo.loader,
|
||||
mlinfo.minecraft,
|
||||
mlinfo.loader_version,
|
||||
unpath
|
||||
).catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
);
|
||||
}
|
||||
const latest = Date.now();
|
||||
this.message.finish(first, latest); //完成
|
||||
if (config.oaf) {
|
||||
await execPromise(`start ${p.join("./instance")}`).catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
await execPromise(`start ${p.join("./instance")}`);
|
||||
}
|
||||
//await this._unzip(buffer);
|
||||
|
||||
logger.info(`Task completed in ${latest - first}ms`);
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
logger.error("Main process failed", err);
|
||||
this.message.handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
private async _getinfo(buffer: Buffer) {
|
||||
const important_name = ["manifest.json", "modrinth.index.json"];
|
||||
let contain: string = "";
|
||||
let info: any = {};
|
||||
const importantFiles = ["manifest.json", "modrinth.index.json"];
|
||||
const zip = await yauzl_promise(buffer);
|
||||
|
||||
for await (const entry of zip) {
|
||||
if (important_name.includes(entry.fileName)) {
|
||||
contain = entry.fileName;
|
||||
info = JSON.parse((await entry.ReadEntry).toString());
|
||||
break;
|
||||
if (importantFiles.includes(entry.fileName)) {
|
||||
const content = await entry.ReadEntry;
|
||||
const info = JSON.parse(content.toString());
|
||||
logger.debug("Found important file", { fileName: entry.fileName, info });
|
||||
return { contain: entry.fileName, info };
|
||||
}
|
||||
}
|
||||
return { contain, info };
|
||||
|
||||
throw new Error("No manifest file found in modpack");
|
||||
}
|
||||
|
||||
private async _unzip(buffer: Buffer, instancename: string) {
|
||||
/* 解压Zip */
|
||||
logger.info("Starting unzip process", { instancename });
|
||||
const zip = await yauzl_promise(buffer);
|
||||
const instancePath = `./instance/${instancename}`;
|
||||
let index = 1;
|
||||
|
||||
for await (const entry of zip) {
|
||||
const ew = entry.fileName.endsWith("/");
|
||||
if (ew) {
|
||||
await fs.promises.mkdir(
|
||||
`./instance/${instancename}/${entry.fileName}`,
|
||||
{
|
||||
recursive: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!ew && entry.fileName.startsWith("overrides/")) {
|
||||
const dirPath = `./instance/${instancename}/${entry.fileName
|
||||
.substring(0, entry.fileName.lastIndexOf("/"))
|
||||
.replace("overrides/", "")}`;
|
||||
const isDir = entry.fileName.endsWith("/");
|
||||
|
||||
if (isDir) {
|
||||
await fs.promises.mkdir(`${instancePath}/${entry.fileName}`, {
|
||||
recursive: true,
|
||||
});
|
||||
} else if (entry.fileName.startsWith("overrides/")) {
|
||||
// 跳过黑名单文件
|
||||
if (this._ublack(entry.fileName)) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 创建目标目录
|
||||
const targetPath = entry.fileName.replace("overrides/", "");
|
||||
const dirPath = `${instancePath}/${targetPath.substring(0, targetPath.lastIndexOf("/"))}`;
|
||||
await fs.promises.mkdir(dirPath, { recursive: true });
|
||||
|
||||
// 解压文件
|
||||
const stream = await entry.openReadStream;
|
||||
const write = fs.createWriteStream(
|
||||
`./instance/${instancename}/${entry.fileName.replace(
|
||||
"overrides/",
|
||||
""
|
||||
)}`
|
||||
);
|
||||
const write = fs.createWriteStream(`${instancePath}/${targetPath}`);
|
||||
await pipeline(stream, write);
|
||||
}
|
||||
|
||||
this.message.unzip(entry.fileName, zip.length, index);
|
||||
// this.ws.send(
|
||||
// JSON.stringify({
|
||||
// status: "unzip",
|
||||
// result: { name: entry.fileName, total: zip.length, current: index },
|
||||
// })
|
||||
// );
|
||||
index++;
|
||||
}
|
||||
/* 解压完成 */
|
||||
|
||||
logger.info("Unzip process completed", { instancename, totalFiles: zip.length });
|
||||
}
|
||||
|
||||
private _ublack(filename: string) {
|
||||
if (filename === "overrides/") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
filename.includes("shaderpacks") ||
|
||||
filename.includes("essential") ||
|
||||
filename.includes("resourcepacks") ||
|
||||
filename === "overrides/options.txt"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 检查文件是否在解压黑名单中
|
||||
* @param filename 文件名
|
||||
* @returns 是否在黑名单中
|
||||
*/
|
||||
private _ublack(filename: string): boolean {
|
||||
const blacklist = [
|
||||
"overrides/",
|
||||
"overrides/options.txt",
|
||||
"shaderpacks",
|
||||
"essential",
|
||||
"resourcepacks"
|
||||
];
|
||||
|
||||
return blacklist.some(item => filename.includes(item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import websocket, { WebSocketServer } from "ws"
|
||||
import { createServer, Server } from "node:http";
|
||||
import { Config, IConfig } from "./utils/config.js";
|
||||
import { Dex } from "./Dex.js";
|
||||
import { exec } from "node:child_process";
|
||||
import { logger } from "./utils/logger.js";
|
||||
import { checkJava, JavaCheckResult } from "./utils/utils.js";
|
||||
export class Core {
|
||||
private config: IConfig;
|
||||
private readonly app: Application;
|
||||
@@ -27,56 +28,119 @@ export class Core {
|
||||
this.dex = new Dex(this.ws)
|
||||
}
|
||||
|
||||
javachecker(){
|
||||
exec("java -version",(err,stdout,stderr)=>{
|
||||
if(err){
|
||||
this.wsx.send(JSON.stringify({
|
||||
type:"error",
|
||||
message:"jini"
|
||||
}))
|
||||
private async javachecker() {
|
||||
try {
|
||||
const result: JavaCheckResult = await checkJava();
|
||||
|
||||
if (result.exists && result.version) {
|
||||
logger.info(`Java detected: ${result.version.fullVersion} (${result.version.vendor})`);
|
||||
|
||||
if (this.wsx) {
|
||||
this.wsx.send(JSON.stringify({
|
||||
type: "info",
|
||||
message: `Java detected: ${result.version.fullVersion} (${result.version.vendor})`,
|
||||
data: result.version
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
logger.error("Java check failed", result.error);
|
||||
|
||||
if (this.wsx) {
|
||||
this.wsx.send(JSON.stringify({
|
||||
type: "error",
|
||||
message: result.error || "Java not found or version check failed",
|
||||
data: result
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error("Java check exception", error as Error);
|
||||
|
||||
if (this.wsx) {
|
||||
this.wsx.send(JSON.stringify({
|
||||
type: "error",
|
||||
message: "Java check encountered an exception"
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
express() {
|
||||
private express() {
|
||||
this.app.use(cors());
|
||||
this.app.use(express.json());
|
||||
this.app.get('/',(req,res)=>{
|
||||
res.json({
|
||||
status:200,
|
||||
by:"DeEarthX.Core",
|
||||
qqg:"559349662",
|
||||
bilibili:"https://space.bilibili.com/1728953419"
|
||||
})
|
||||
})
|
||||
this.app.post("/start", this.upload.single("file"), (req, res) => {
|
||||
if (!req.file) {
|
||||
return;
|
||||
}
|
||||
if (!req.query.mode){
|
||||
return;
|
||||
}
|
||||
this.dex.Main(req.file.buffer,req.query.mode == "server") //Dex
|
||||
//this.dex.Main(req.file.buffer)
|
||||
res.json({
|
||||
status:200,
|
||||
message:"task is peding"
|
||||
})
|
||||
})
|
||||
this.app.get('/config/get', (req, res) => {
|
||||
res.json(this.config)
|
||||
})
|
||||
|
||||
// 健康检查路由
|
||||
this.app.get('/', (req, res) => {
|
||||
res.json({
|
||||
status: 200,
|
||||
by: "DeEarthX.Core",
|
||||
qqg: "559349662",
|
||||
bilibili: "https://space.bilibili.com/1728953419"
|
||||
});
|
||||
});
|
||||
|
||||
// 启动任务路由
|
||||
this.app.post("/start", this.upload.single("file"), (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ status: 400, message: "No file uploaded" });
|
||||
}
|
||||
if (!req.query.mode) {
|
||||
return res.status(400).json({ status: 400, message: "Mode parameter missing" });
|
||||
}
|
||||
|
||||
const isServerMode = req.query.mode === "server";
|
||||
logger.info("Starting task", { isServerMode });
|
||||
|
||||
// 非阻塞执行主要任务
|
||||
this.dex.Main(req.file.buffer, isServerMode).catch(err => {
|
||||
logger.error("Task execution failed", err);
|
||||
});
|
||||
|
||||
res.json({ status: 200, message: "Task is pending" });
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
logger.error("/start route error", error);
|
||||
res.status(500).json({ status: 500, message: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取配置路由
|
||||
this.app.get('/config/get', (req, res) => {
|
||||
try {
|
||||
res.json(this.config);
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
logger.error("/config/get route error", error);
|
||||
res.status(500).json({ status: 500, message: "Failed to get config" });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新配置路由
|
||||
this.app.post('/config/post', (req, res) => {
|
||||
Config.write_config(req.body)
|
||||
res.json({ status: 200 })
|
||||
})
|
||||
try {
|
||||
Config.writeConfig(req.body);
|
||||
this.config = req.body; // 更新内存中的配置
|
||||
logger.info("Config updated");
|
||||
res.json({ status: 200 });
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
logger.error("/config/post route error", error);
|
||||
res.status(500).json({ status: 500, message: "Failed to update config" });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start() {
|
||||
this.express()
|
||||
this.server.listen(37019, () => {
|
||||
console.log("Server is running on http://localhost:37019")
|
||||
})
|
||||
public async start() {
|
||||
this.express();
|
||||
this.server.listen(37019, async () => {
|
||||
logger.info("Server is running on http://localhost:37019");
|
||||
await this.javachecker(); // 启动时检查Java
|
||||
});
|
||||
|
||||
// 处理服务器错误
|
||||
this.server.on('error', (err) => {
|
||||
logger.error("Server error", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,39 +3,55 @@ import { Forge } from "./forge.js";
|
||||
import { Minecraft } from "./minecraft.js";
|
||||
import { NeoForge } from "./neoforge.js";
|
||||
|
||||
/**
|
||||
* 模组加载器接口
|
||||
*/
|
||||
interface XModloader {
|
||||
setup(): Promise<void>;
|
||||
installer(): Promise<void>;
|
||||
}
|
||||
export function modloader(ml:string,mcv:string,mlv:string,path:string){
|
||||
let modloader:XModloader
|
||||
switch (ml) {
|
||||
/**
|
||||
* 创建模组加载器实例
|
||||
* @param ml 加载器类型
|
||||
* @param mcv Minecraft版本
|
||||
* @param mlv 加载器版本
|
||||
* @param path 安装路径
|
||||
* @returns 加载器实例
|
||||
*/
|
||||
export function modloader(ml: string, mcv: string, mlv: string, path: string): XModloader {
|
||||
switch (ml) {
|
||||
case "fabric":
|
||||
modloader = new Fabric(mcv,mlv,path)
|
||||
break;
|
||||
case "fabric-loader":
|
||||
modloader = new Fabric(mcv,mlv,path)
|
||||
break;
|
||||
return new Fabric(mcv, mlv, path);
|
||||
case "forge":
|
||||
modloader = new Forge(mcv,mlv,path)
|
||||
break;
|
||||
return new Forge(mcv, mlv, path);
|
||||
case "neoforge":
|
||||
modloader = new NeoForge(mcv,mlv,path)
|
||||
break;
|
||||
return new NeoForge(mcv, mlv, path);
|
||||
default:
|
||||
modloader = new Minecraft(ml,mcv,mlv,path)
|
||||
break;
|
||||
}
|
||||
return modloader
|
||||
return new Minecraft(ml, mcv, mlv, path);
|
||||
}
|
||||
}
|
||||
|
||||
export async function mlsetup(ml:string,mcv:string,mlv:string,path:string){
|
||||
const minecraft = new Minecraft(ml,mcv,mlv,path);
|
||||
//console.log(ml)
|
||||
await modloader(ml,mcv,mlv,path).setup()
|
||||
await minecraft.setup()
|
||||
/**
|
||||
* 设置模组加载器
|
||||
* @param ml 加载器类型
|
||||
* @param mcv Minecraft版本
|
||||
* @param mlv 加载器版本
|
||||
* @param path 安装路径
|
||||
*/
|
||||
export async function mlsetup(ml: string, mcv: string, mlv: string, path: string): Promise<void> {
|
||||
const minecraft = new Minecraft(ml, mcv, mlv, path);
|
||||
await modloader(ml, mcv, mlv, path).setup();
|
||||
await minecraft.setup();
|
||||
}
|
||||
|
||||
export async function dinstall(ml:string,mcv:string,mlv:string,path:string){
|
||||
await modloader(ml,mcv,mlv,path).installer();
|
||||
/**
|
||||
* 安装模组加载器
|
||||
* @param ml 加载器类型
|
||||
* @param mcv Minecraft版本
|
||||
* @param mlv 加载器版本
|
||||
* @param path 安装路径
|
||||
*/
|
||||
export async function dinstall(ml: string, mcv: string, mlv: string, path: string): Promise<void> {
|
||||
await modloader(ml, mcv, mlv, path).installer();
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
/**
|
||||
* 应用配置接口
|
||||
*/
|
||||
export interface IConfig {
|
||||
mirror: {
|
||||
bmclapi: boolean;
|
||||
@@ -9,32 +13,65 @@ export interface IConfig {
|
||||
dexpub: boolean;
|
||||
mixins: boolean;
|
||||
};
|
||||
oaf: boolean
|
||||
oaf: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
*/
|
||||
const DEFAULT_CONFIG: IConfig = {
|
||||
mirror: {
|
||||
bmclapi: true,
|
||||
mcimirror: true,
|
||||
},
|
||||
filter: {
|
||||
hashes: true,
|
||||
dexpub: false,
|
||||
mixins: true,
|
||||
},
|
||||
oaf: true
|
||||
};
|
||||
|
||||
/**
|
||||
* 配置文件路径
|
||||
*/
|
||||
const CONFIG_PATH = "./config.json";
|
||||
|
||||
/**
|
||||
* 配置管理器
|
||||
*/
|
||||
export class Config {
|
||||
private readonly default_config: IConfig = {
|
||||
mirror: {
|
||||
bmclapi: true,
|
||||
mcimirror: true,
|
||||
},
|
||||
filter: {
|
||||
hashes: true,
|
||||
dexpub: false,
|
||||
mixins: true,
|
||||
},
|
||||
oaf:true
|
||||
};
|
||||
config(): IConfig {
|
||||
if (!fs.existsSync("./config.json")) {
|
||||
fs.writeFileSync("./config.json", JSON.stringify(this.default_config));
|
||||
return this.default_config;
|
||||
/**
|
||||
* 获取配置
|
||||
* @returns 配置对象
|
||||
*/
|
||||
public static getConfig(): IConfig {
|
||||
if (!fs.existsSync(CONFIG_PATH)) {
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
||||
return DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
||||
return JSON.parse(content);
|
||||
} catch (err) {
|
||||
console.error("Failed to read config file, using defaults", err);
|
||||
return DEFAULT_CONFIG;
|
||||
}
|
||||
return JSON.parse(fs.readFileSync("./config.json", "utf-8"));
|
||||
}
|
||||
static write_config(config: IConfig) {
|
||||
fs.writeFileSync("./config.json", JSON.stringify(config));
|
||||
|
||||
/**
|
||||
* 写入配置
|
||||
* @param config 配置对象
|
||||
*/
|
||||
public static writeConfig(config: IConfig): void {
|
||||
try {
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
||||
} catch (err) {
|
||||
console.error("Failed to write config file", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Config().config();
|
||||
// 默认导出配置实例
|
||||
export default Config.getConfig();
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
const env = process.env.DEBUG;
|
||||
const isDebug = env === "true";
|
||||
|
||||
export function debug(msg: any){
|
||||
if (env === "true"){
|
||||
if(msg instanceof Error){
|
||||
console.log(`[ERROR] [${new Date().toLocaleString()}] `);
|
||||
console.log(msg);
|
||||
}
|
||||
if (typeof msg === "string"){
|
||||
console.log(`[DEBUG] [${new Date().toLocaleString()}] ` + msg);
|
||||
}
|
||||
if (typeof msg === "object"){
|
||||
console.log(`[OBJ] [${new Date().toLocaleString()}] `);
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
// 日志级别枚举
|
||||
type LogLevel = "debug" | "info" | "warn" | "error";
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
* @param level 日志级别
|
||||
* @param msg 日志内容
|
||||
* @param data 附加数据(可选)
|
||||
*/
|
||||
function log(level: LogLevel, msg: string | Error, data?: any) {
|
||||
const timestamp = new Date().toLocaleString();
|
||||
const prefix = `[${level.toUpperCase()}] [${timestamp}]`;
|
||||
|
||||
// 确保只在调试模式下输出debug日志
|
||||
if (level === "debug" && !isDebug) return;
|
||||
|
||||
if (msg instanceof Error) {
|
||||
console.error(`${prefix} ${msg.message}`);
|
||||
console.error(msg.stack);
|
||||
if (data) console.error(`${prefix} Data:`, data);
|
||||
} else {
|
||||
const logFunc = level === "error" ? console.error :
|
||||
level === "warn" ? console.warn :
|
||||
console.log;
|
||||
|
||||
logFunc(`${prefix} ${msg}`);
|
||||
if (data) logFunc(`${prefix} Data:`, data);
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
debug: (msg: string | Error, data?: any) => log("debug", msg, data),
|
||||
info: (msg: string | Error, data?: any) => log("info", msg, data),
|
||||
warn: (msg: string | Error, data?: any) => log("warn", msg, data),
|
||||
error: (msg: string | Error, data?: any) => log("error", msg, data)
|
||||
};
|
||||
|
||||
// 保持向后兼容
|
||||
export const debug = logger.debug;
|
||||
|
||||
@@ -6,7 +6,29 @@ import fs from "node:fs";
|
||||
import fse from "fs-extra";
|
||||
import { WebSocket } from "ws";
|
||||
import { ExecOptions, exec} from "node:child_process";
|
||||
|
||||
/**
|
||||
* Java版本信息接口
|
||||
*/
|
||||
export interface JavaVersion {
|
||||
major: number;
|
||||
minor: number;
|
||||
patch: number;
|
||||
fullVersion: string;
|
||||
vendor: string;
|
||||
runtimeVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Java检测结果接口
|
||||
*/
|
||||
export interface JavaCheckResult {
|
||||
exists: boolean;
|
||||
version?: JavaVersion;
|
||||
error?: string;
|
||||
}
|
||||
import { MessageWS } from "./ws.js";
|
||||
import { logger } from "./logger.js";
|
||||
|
||||
export class Utils {
|
||||
public modrinth_url: string;
|
||||
@@ -50,90 +72,157 @@ export function version_compare(v1: string, v2: string) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测Java是否安装并获取版本信息
|
||||
* @returns Java检测结果
|
||||
*/
|
||||
export async function checkJava(): Promise<JavaCheckResult> {
|
||||
try {
|
||||
const output = await new Promise<string>((resolve, reject) => {
|
||||
exec("java -version", (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
logger.error("Java check failed", err);
|
||||
reject(new Error("Java not found"));
|
||||
return;
|
||||
}
|
||||
// Java版本信息输出在stderr中
|
||||
resolve(stderr);
|
||||
});
|
||||
});
|
||||
|
||||
logger.debug(`Java version output: ${output}`);
|
||||
|
||||
// 解析Java版本信息
|
||||
const versionRegex = /version "(\d+)(\.(\d+))?(\.(\d+))?/;
|
||||
const vendorRegex = /(Java\(TM\)|OpenJDK).*Runtime Environment.*by (.*)/;
|
||||
|
||||
const versionMatch = output.match(versionRegex);
|
||||
const vendorMatch = output.match(vendorRegex);
|
||||
|
||||
if (!versionMatch) {
|
||||
return {
|
||||
exists: true,
|
||||
error: "Failed to parse Java version"
|
||||
};
|
||||
}
|
||||
|
||||
const major = parseInt(versionMatch[1], 10);
|
||||
const minor = versionMatch[3] ? parseInt(versionMatch[3], 10) : 0;
|
||||
const patch = versionMatch[5] ? parseInt(versionMatch[5], 10) : 0;
|
||||
|
||||
const versionInfo: JavaVersion = {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
fullVersion: versionMatch[0].replace("version ", ""),
|
||||
vendor: vendorMatch ? vendorMatch[2] : "Unknown"
|
||||
};
|
||||
|
||||
logger.info(`Java detected: ${JSON.stringify(versionInfo)}`);
|
||||
|
||||
return {
|
||||
exists: true,
|
||||
version: versionInfo
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error("Java check error", error as Error);
|
||||
return {
|
||||
exists: false,
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function execPromise(cmd:string,options?:ExecOptions){
|
||||
logger.debug(`Executing command: ${cmd}`);
|
||||
return new Promise((resolve,reject)=>{
|
||||
exec(cmd,options,(err,stdout,stderr)=>{
|
||||
if(err){
|
||||
logger.error(`Command execution failed: ${cmd}`, err);
|
||||
logger.debug(`Stderr: ${stderr}`);
|
||||
reject(err)
|
||||
return;
|
||||
}
|
||||
if (stdout) logger.debug(`Command stdout: ${stdout}`);
|
||||
if (stderr) logger.debug(`Command stderr: ${stderr}`);
|
||||
}).on('exit',(code)=>{
|
||||
logger.debug(`Command completed with exit code: ${code}`);
|
||||
resolve(code)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function fastdownload(data: [string, string]|string[][]) {
|
||||
let _data = undefined;
|
||||
if(Array.isArray(data[0])){
|
||||
_data = data
|
||||
}else{
|
||||
_data = [data]
|
||||
}
|
||||
// 确保downloadList始终是[string, string][]类型
|
||||
const downloadList: [string, string][] = Array.isArray(data[0])
|
||||
? (data as string[][]).map(item => item as [string, string])
|
||||
: [data as [string, string]];
|
||||
logger.info(`Starting fast download of ${downloadList.length} files`);
|
||||
|
||||
return await pMap(
|
||||
_data,
|
||||
async (e:any) => {
|
||||
downloadList,
|
||||
async (item: [string, string]) => {
|
||||
const [url, filePath] = item;
|
||||
try {
|
||||
await pRetry(
|
||||
async () => {
|
||||
if (!fs.existsSync(e[1])) {
|
||||
await got
|
||||
.get(e[0], {
|
||||
responseType: "buffer",
|
||||
headers: {
|
||||
"user-agent": "DeEarthX",
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
fse.outputFileSync(e[1], res.rawBody);
|
||||
});
|
||||
if (!fs.existsSync(filePath)) {
|
||||
logger.debug(`Downloading ${url} to ${filePath}`);
|
||||
const res = await got.get(url, {
|
||||
responseType: "buffer",
|
||||
headers: { "user-agent": "DeEarthX" },
|
||||
});
|
||||
fse.outputFileSync(filePath, res.rawBody);
|
||||
logger.debug(`Downloaded ${url} successfully`);
|
||||
} else {
|
||||
logger.debug(`File already exists, skipping: ${filePath}`);
|
||||
}
|
||||
},
|
||||
{ retries: 3 }
|
||||
{ retries: 3, onFailedAttempt: (error) => {
|
||||
logger.warn(`Download attempt failed for ${url}, retrying (${error.attemptNumber}/3)`);
|
||||
}}
|
||||
);
|
||||
} catch (e) {
|
||||
//LOGGER.error({ err: e });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to download ${url} after 3 attempts`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{ concurrency: 16 }
|
||||
);
|
||||
}
|
||||
|
||||
export async function Wfastdownload(data: string[][],ws:MessageWS) {
|
||||
let index = 1;
|
||||
export async function Wfastdownload(data: string[][], ws: MessageWS) {
|
||||
logger.info(`Starting web download of ${data.length} files`);
|
||||
|
||||
return await pMap(
|
||||
data,
|
||||
async (e:any) => {
|
||||
async (item: string[], idx: number) => {
|
||||
const [url, filePath] = item;
|
||||
try {
|
||||
await pRetry(
|
||||
async () => {
|
||||
if (!fs.existsSync(e[1])) {
|
||||
await got
|
||||
.get(e[0], {
|
||||
responseType: "buffer",
|
||||
headers: {
|
||||
"user-agent": "DeEarthX",
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
fse.outputFileSync(e[1], res.rawBody);
|
||||
});
|
||||
if (!fs.existsSync(filePath)) {
|
||||
logger.debug(`Downloading ${url} to ${filePath}`);
|
||||
const res = await got.get(url, {
|
||||
responseType: "buffer",
|
||||
headers: { "user-agent": "DeEarthX" },
|
||||
});
|
||||
fse.outputFileSync(filePath, res.rawBody);
|
||||
logger.debug(`Downloaded ${url} successfully`);
|
||||
} else {
|
||||
logger.debug(`File already exists, skipping: ${filePath}`);
|
||||
}
|
||||
ws.download(data.length,index,e[1])
|
||||
// ws.send(JSON.stringify({
|
||||
// status:"downloading",
|
||||
// result:{
|
||||
// total:data.length,
|
||||
// index:index,
|
||||
// name:e[1]
|
||||
// }
|
||||
// }))
|
||||
index++
|
||||
|
||||
// 更新下载进度
|
||||
ws.download(data.length, idx + 1, filePath);
|
||||
},
|
||||
{ retries: 3 }
|
||||
{ retries: 3, onFailedAttempt: (error) => {
|
||||
logger.warn(`Download attempt failed for ${url}, retrying (${error.attemptNumber}/3)`);
|
||||
}}
|
||||
);
|
||||
} catch (e) {
|
||||
//LOGGER.error({ err: e });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to download ${url} after 3 attempts`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{ concurrency: 16 }
|
||||
|
||||
@@ -1,56 +1,83 @@
|
||||
import websocket, { WebSocketServer } from "ws";
|
||||
import { logger } from "./logger.js";
|
||||
|
||||
export class MessageWS {
|
||||
ws!: websocket;
|
||||
private ws!: websocket;
|
||||
|
||||
constructor(ws: websocket) {
|
||||
this.ws = ws;
|
||||
|
||||
// 监听WebSocket错误
|
||||
this.ws.on('error', (err) => {
|
||||
logger.error("WebSocket error", err);
|
||||
});
|
||||
|
||||
// 监听连接关闭
|
||||
this.ws.on('close', (code, reason) => {
|
||||
logger.info("WebSocket connection closed", { code, reason: reason.toString() });
|
||||
});
|
||||
}
|
||||
|
||||
finish(first: number, latest: number) {
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
status: "finish",
|
||||
result: latest - first,
|
||||
})
|
||||
);
|
||||
/**
|
||||
* 发送完成消息
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
*/
|
||||
finish(startTime: number, endTime: number) {
|
||||
this.send("finish", endTime - startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送解压进度消息
|
||||
* @param entryName 文件名
|
||||
* @param total 总文件数
|
||||
* @param current 当前文件索引
|
||||
*/
|
||||
unzip(entryName: string, total: number, current: number) {
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
status: "unzip",
|
||||
result: { name: entryName, total, current },
|
||||
})
|
||||
);
|
||||
this.send("unzip", { name: entryName, total, current });
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送下载进度消息
|
||||
* @param total 总文件数
|
||||
* @param index 当前文件索引
|
||||
* @param name 文件名
|
||||
*/
|
||||
download(total: number, index: number, name: string) {
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
status: "downloading",
|
||||
result: {
|
||||
total,
|
||||
index,
|
||||
name,
|
||||
},
|
||||
})
|
||||
);
|
||||
this.send("downloading", { total, index, name });
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送状态变更消息
|
||||
*/
|
||||
statusChange() {
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
status: "changed",
|
||||
result: undefined,
|
||||
})
|
||||
);
|
||||
this.send("changed", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误消息
|
||||
* @param error 错误对象
|
||||
*/
|
||||
handleError(error: Error) {
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
status: "error",
|
||||
result: error.message,
|
||||
})
|
||||
);
|
||||
this.send("error", error.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用消息发送方法
|
||||
* @param status 消息状态
|
||||
* @param result 消息内容
|
||||
*/
|
||||
private send(status: string, result: any) {
|
||||
try {
|
||||
if (this.ws.readyState === websocket.OPEN) {
|
||||
const message = JSON.stringify({ status, result });
|
||||
logger.debug("Sending WebSocket message", { status, result });
|
||||
this.ws.send(message);
|
||||
} else {
|
||||
logger.warn(`WebSocket not open, cannot send message: ${status}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Failed to send WebSocket message", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user