mirror of
https://github.com/MCSManager/MCSManager.git
synced 2025-04-06 17:10:29 +08:00
Refactor: common project created
This commit is contained in:
parent
ecc77480dc
commit
a0b8944f13
1578
common/package-lock.json
generated
Normal file
1578
common/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
common/package.json
Normal file
23
common/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "common",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc --project tsconfig.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/os-utils": "^0.0.4",
|
||||
"archiver": "^6.0.1",
|
||||
"compressing": "^1.10.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"os-utils": "^0.0.14",
|
||||
"socket.io": "^4.7.2"
|
||||
}
|
||||
}
|
0
common/src/common_const.ts
Normal file
0
common/src/common_const.ts
Normal file
19
common/src/global_variable.ts
Executable file
19
common/src/global_variable.ts
Executable file
@ -0,0 +1,19 @@
|
||||
export default class GlobalVariable {
|
||||
public static readonly map = new Map<string, any>();
|
||||
|
||||
public static set(k: string, v: any) {
|
||||
GlobalVariable.map.set(k, v);
|
||||
}
|
||||
|
||||
public static get(k: string, def?: any) {
|
||||
if (GlobalVariable.map.has(k)) {
|
||||
return GlobalVariable.map.get(k);
|
||||
} else {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
public static del(k: string) {
|
||||
return GlobalVariable.map.delete(k);
|
||||
}
|
||||
}
|
40
common/src/index.ts
Normal file
40
common/src/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import StorageSubsystem from "./system_storage";
|
||||
import GlobalVariable from "./global_variable";
|
||||
import InstanceStreamListener from "./instance_stream";
|
||||
import { processWrapper, killProcess } from "./process_tools";
|
||||
import { systemInfo } from "./system_info";
|
||||
import {
|
||||
QueryMapWrapper,
|
||||
IDataSource,
|
||||
MySqlSource,
|
||||
LocalFileSource,
|
||||
QueryWrapper
|
||||
} from "./query_wrapper";
|
||||
import {
|
||||
configureEntityParams,
|
||||
toText,
|
||||
toBoolean,
|
||||
toNumber,
|
||||
isEmpty,
|
||||
supposeValue
|
||||
} from "./typecheck";
|
||||
|
||||
export {
|
||||
StorageSubsystem,
|
||||
GlobalVariable,
|
||||
InstanceStreamListener,
|
||||
processWrapper,
|
||||
QueryMapWrapper,
|
||||
IDataSource,
|
||||
MySqlSource,
|
||||
LocalFileSource,
|
||||
QueryWrapper,
|
||||
killProcess,
|
||||
configureEntityParams,
|
||||
toText,
|
||||
toBoolean,
|
||||
toNumber,
|
||||
isEmpty,
|
||||
supposeValue,
|
||||
systemInfo
|
||||
};
|
51
common/src/instance_stream.ts
Executable file
51
common/src/instance_stream.ts
Executable file
@ -0,0 +1,51 @@
|
||||
import { Socket } from "socket.io";
|
||||
|
||||
// Application instance data stream forwarding adapter
|
||||
|
||||
export default class InstanceStreamListener {
|
||||
// Instance uuid -> Socket[]
|
||||
public readonly listenMap = new Map<string, Socket[]>();
|
||||
|
||||
public requestForward(socket: Socket, instanceUuid: string) {
|
||||
if (this.listenMap.has(instanceUuid)) {
|
||||
const sockets = this.listenMap.get(instanceUuid);
|
||||
for (const iterator of sockets)
|
||||
if (iterator.id === socket.id)
|
||||
throw new Error(
|
||||
`This Socket ${socket.id} already exists in the specified instance listening table`
|
||||
);
|
||||
sockets.push(socket);
|
||||
} else {
|
||||
this.listenMap.set(instanceUuid, [socket]);
|
||||
}
|
||||
}
|
||||
|
||||
public cannelForward(socket: Socket, instanceUuid: string) {
|
||||
if (!this.listenMap.has(instanceUuid))
|
||||
throw new Error(`The specified ${instanceUuid} does not exist in the listening table`);
|
||||
const socketList = this.listenMap.get(instanceUuid);
|
||||
socketList.forEach((v, index) => {
|
||||
if (v.id === socket.id) socketList.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
public forward(instanceUuid: string, data: any) {
|
||||
const sockets = this.listenMap.get(instanceUuid);
|
||||
sockets.forEach((socket) => {
|
||||
if (socket && socket.connected) socket.emit("instance/stdout", data);
|
||||
});
|
||||
}
|
||||
|
||||
public forwardViaCallback(instanceUuid: string, callback: (socket: Socket) => void) {
|
||||
if (this.listenMap.has(instanceUuid)) {
|
||||
const sockets = this.listenMap.get(instanceUuid);
|
||||
sockets.forEach((socket) => {
|
||||
if (socket && socket.connected) callback(socket);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public hasListenInstance(instanceUuid: string) {
|
||||
return this.listenMap.has(instanceUuid) && this.listenMap.get(instanceUuid).length > 0;
|
||||
}
|
||||
}
|
80
common/src/mcping.ts
Executable file
80
common/src/mcping.ts
Executable file
@ -0,0 +1,80 @@
|
||||
// Using SLT (Server List Ping) provided by Minecraft.
|
||||
// Since it is part of the protocol it is always enabled contrary to Query
|
||||
// More information at: https://wiki.vg/Server_List_Ping#Response
|
||||
// Github: https://github.com/Vaksty/mcping
|
||||
|
||||
import net from "net";
|
||||
|
||||
function formatMotd(motd: any) {
|
||||
let noSpaces = motd.replace(/\u0000/g, "");
|
||||
Buffer.from(noSpaces);
|
||||
// let noColor = noSpaces.toString().replace(/[^\x00-\x7F]/g, '');
|
||||
// console.log(Buffer.from(motd, 'utf8').toString('hex'));
|
||||
// console.log(noColor);
|
||||
}
|
||||
|
||||
export default class MCServStatus {
|
||||
public port: number;
|
||||
public host: string;
|
||||
public status: any;
|
||||
|
||||
constructor(port: number, host: string) {
|
||||
this.port = port;
|
||||
this.host = host;
|
||||
this.status = {
|
||||
online: null,
|
||||
version: null,
|
||||
motd: null,
|
||||
current_players: null,
|
||||
max_players: null,
|
||||
latency: null
|
||||
};
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var start_time = new Date().getTime();
|
||||
const client = net.connect(this.port, this.host, () => {
|
||||
this.status.latency = Math.round(new Date().getTime() - start_time);
|
||||
// 0xFE packet identifier for a server list ping
|
||||
// 0x01 server list ping's payload (always 1)
|
||||
let data = Buffer.from([0xfe, 0x01]);
|
||||
client.write(data);
|
||||
});
|
||||
|
||||
// The client can also receive data from the server by reading from its socket.
|
||||
client.on("data", (response: any) => {
|
||||
// Check the readme for a simple explanation
|
||||
var server_info = response.toString().split("\x00\x00");
|
||||
|
||||
this.status = {
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
status: true,
|
||||
version: server_info[2].replace(/\u0000/g, ""),
|
||||
motd: server_info[3].replace(/\u0000/g, ""),
|
||||
current_players: server_info[4].replace(/\u0000/g, ""),
|
||||
max_players: server_info[5].replace(/\u0000/g, ""),
|
||||
latency: this.status.latency
|
||||
};
|
||||
formatMotd(server_info[3]);
|
||||
// Request an end to the connection after the data has been received.
|
||||
client.end();
|
||||
resolve(this.status);
|
||||
});
|
||||
|
||||
client.on("end", () => {
|
||||
// console.log('Requested an end to the TCP connection');
|
||||
});
|
||||
|
||||
client.on("error", (err: any) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async asyncStatus() {
|
||||
let status = await this.getStatus();
|
||||
return status;
|
||||
}
|
||||
}
|
120
common/src/process_tools.ts
Executable file
120
common/src/process_tools.ts
Executable file
@ -0,0 +1,120 @@
|
||||
import { ChildProcess, exec, execSync, SpawnOptionsWithoutStdio } from "child_process";
|
||||
import os from "os";
|
||||
import child_process from "child_process";
|
||||
import path from "path";
|
||||
import EventEmitter from "events";
|
||||
import iconv from "iconv-lite";
|
||||
|
||||
export class processWrapper extends EventEmitter {
|
||||
public process: ChildProcess;
|
||||
public pid: number;
|
||||
|
||||
constructor(
|
||||
public readonly file: string,
|
||||
public readonly args: string[],
|
||||
public readonly cwd: string,
|
||||
public readonly timeout: number = null,
|
||||
public readonly code = "utf-8",
|
||||
public readonly option: SpawnOptionsWithoutStdio = {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public start(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeTask: NodeJS.Timeout = null;
|
||||
const process = child_process.spawn(this.file, this.args, {
|
||||
stdio: "pipe",
|
||||
windowsHide: true,
|
||||
cwd: path.normalize(this.cwd),
|
||||
...this.option
|
||||
});
|
||||
this.process = process;
|
||||
this.pid = process.pid;
|
||||
|
||||
this.emit("start", process.pid);
|
||||
if (!process || !process.pid) return reject(false);
|
||||
|
||||
process.stdout.on("data", (text) => this.emit("data", iconv.decode(text, this.code)));
|
||||
process.stderr.on("data", (text) => this.emit("data", iconv.decode(text, this.code)));
|
||||
process.on("exit", (code) => {
|
||||
try {
|
||||
this.emit("exit", code);
|
||||
this.destroy();
|
||||
} catch (error) {}
|
||||
if (timeTask) clearTimeout(timeTask);
|
||||
if (code != 0) return resolve(false);
|
||||
return resolve(true);
|
||||
});
|
||||
|
||||
// timeout, terminate the task
|
||||
if (this.timeout) {
|
||||
timeTask = setTimeout(() => {
|
||||
killProcess(process.pid, process);
|
||||
reject(false);
|
||||
}, 1000 * this.timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getPid() {
|
||||
return this.process.pid;
|
||||
}
|
||||
|
||||
public write(data?: string) {
|
||||
return this.process.stdin.write(iconv.encode(data, this.code));
|
||||
}
|
||||
|
||||
public kill() {
|
||||
killProcess(this.process.pid, this.process);
|
||||
}
|
||||
|
||||
public status() {
|
||||
return this.process.exitCode == null;
|
||||
}
|
||||
|
||||
public exitCode() {
|
||||
return this.process.exitCode;
|
||||
}
|
||||
|
||||
private async destroy() {
|
||||
try {
|
||||
for (const n of this.eventNames()) this.removeAllListeners(n);
|
||||
if (this.process.stdout)
|
||||
for (const eventName of this.process.stdout.eventNames())
|
||||
this.process.stdout.removeAllListeners(eventName);
|
||||
if (this.process.stderr)
|
||||
for (const eventName of this.process.stderr.eventNames())
|
||||
this.process.stderr.removeAllListeners(eventName);
|
||||
if (this.process)
|
||||
for (const eventName of this.process.eventNames())
|
||||
this.process.removeAllListeners(eventName);
|
||||
this.process?.stdout?.destroy();
|
||||
this.process?.stderr?.destroy();
|
||||
if (this.process?.exitCode === null) {
|
||||
this.process.kill("SIGTERM");
|
||||
this.process.kill("SIGKILL");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ProcessWrapper destroy() Error]", error);
|
||||
} finally {
|
||||
this.process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function killProcess(pid: string | number, process: ChildProcess, signal?: any) {
|
||||
try {
|
||||
if (os.platform() === "win32") {
|
||||
execSync(`taskkill /PID ${pid} /T /F`);
|
||||
return true;
|
||||
}
|
||||
if (os.platform() === "linux") {
|
||||
execSync(`kill -s 9 ${pid}`);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
return signal ? process.kill(signal) : process.kill("SIGKILL");
|
||||
}
|
||||
return signal ? process.kill(signal) : process.kill("SIGKILL");
|
||||
}
|
116
common/src/query_wrapper.ts
Executable file
116
common/src/query_wrapper.ts
Executable file
@ -0,0 +1,116 @@
|
||||
interface IMap {
|
||||
size: number;
|
||||
forEach: (value: any, key?: any) => void;
|
||||
}
|
||||
|
||||
interface Page<T> {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
maxPage: number;
|
||||
total: number;
|
||||
data: T[];
|
||||
}
|
||||
|
||||
// Provide the MAP query interface used by the routing layer
|
||||
export class QueryMapWrapper {
|
||||
constructor(public map: IMap) {}
|
||||
|
||||
select<T>(condition: (v: T) => boolean): T[] {
|
||||
const result: T[] = [];
|
||||
this.map.forEach((v: T) => {
|
||||
if (condition(v)) result.push(v);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
page<T>(data: T[], page = 1, pageSize = 10) {
|
||||
const start = (page - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
let size = data.length;
|
||||
let maxPage = 0;
|
||||
while (size > 0) {
|
||||
size -= pageSize;
|
||||
maxPage++;
|
||||
}
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
maxPage,
|
||||
data: data.slice(start, end)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Data source interface for QueryWrapper to use
|
||||
export interface IDataSource<T> {
|
||||
selectPage: (condition: any, page: number, pageSize: number) => Page<T>;
|
||||
select: (condition: any) => any[];
|
||||
update: (condition: any, data: any) => void;
|
||||
delete: (condition: any) => void;
|
||||
insert: (data: any) => void;
|
||||
}
|
||||
|
||||
// MYSQL data source
|
||||
export class MySqlSource<T> implements IDataSource<T> {
|
||||
selectPage: (condition: any, page: number, pageSize: number) => Page<T>;
|
||||
select: (condition: any) => any[];
|
||||
update: (condition: any, data: any) => void;
|
||||
delete: (condition: any) => void;
|
||||
insert: (data: any) => void;
|
||||
}
|
||||
|
||||
// local file data source (embedded microdatabase)
|
||||
export class LocalFileSource<T> implements IDataSource<T> {
|
||||
constructor(public data: any) {}
|
||||
|
||||
selectPage(condition: any, page = 1, pageSize = 10) {
|
||||
const result: T[] = [];
|
||||
this.data.forEach((v: any) => {
|
||||
for (const key in condition) {
|
||||
const dataValue = v[key];
|
||||
const targetValue = condition[key];
|
||||
if (targetValue[0] == "%") {
|
||||
if (!dataValue.includes(targetValue.slice(1, targetValue.length - 1))) return false;
|
||||
} else {
|
||||
if (targetValue !== dataValue) return false;
|
||||
}
|
||||
}
|
||||
result.push(v);
|
||||
});
|
||||
return this.page(result, page, pageSize);
|
||||
}
|
||||
|
||||
page(data: T[], page = 1, pageSize = 10) {
|
||||
const start = (page - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
let size = data.length;
|
||||
let maxPage = 0;
|
||||
while (size > 0) {
|
||||
size -= pageSize;
|
||||
maxPage++;
|
||||
}
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
maxPage,
|
||||
total: data.length,
|
||||
data: data.slice(start, end)
|
||||
};
|
||||
}
|
||||
|
||||
select(condition: any): any[] {
|
||||
return null;
|
||||
}
|
||||
update(condition: any, data: any) {}
|
||||
delete(condition: any) {}
|
||||
insert(data: any) {}
|
||||
}
|
||||
|
||||
// Provide the unified data query interface used by the routing layer
|
||||
export class QueryWrapper<T> {
|
||||
constructor(public dataSource: IDataSource<T>) {}
|
||||
|
||||
selectPage(condition: any, page = 1, pageSize = 10) {
|
||||
return this.dataSource.selectPage(condition, page, pageSize);
|
||||
}
|
||||
}
|
99
common/src/system_info.ts
Executable file
99
common/src/system_info.ts
Executable file
@ -0,0 +1,99 @@
|
||||
import os from "os";
|
||||
import osUtils from "os-utils";
|
||||
import fs from "fs";
|
||||
// import systeminformation from "systeminformation";
|
||||
|
||||
interface IInfoTable {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
interface ISystemInfo {
|
||||
cpuUsage: number;
|
||||
memUsage: number;
|
||||
totalmem: number;
|
||||
freemem: number;
|
||||
type: string;
|
||||
hostname: string;
|
||||
platform: string;
|
||||
release: string;
|
||||
uptime: number;
|
||||
cwd: string;
|
||||
processCpu: number;
|
||||
processMem: number;
|
||||
loadavg: number[];
|
||||
}
|
||||
|
||||
// System details are updated every time
|
||||
const info: ISystemInfo = {
|
||||
type: os.type(),
|
||||
hostname: os.hostname(),
|
||||
platform: os.platform(),
|
||||
release: os.release(),
|
||||
uptime: os.uptime(),
|
||||
cwd: process.cwd(),
|
||||
loadavg: os.loadavg(),
|
||||
freemem: 0,
|
||||
cpuUsage: 0,
|
||||
memUsage: 0,
|
||||
totalmem: 0,
|
||||
processCpu: 0,
|
||||
processMem: 0
|
||||
};
|
||||
|
||||
// periodically refresh the cache
|
||||
setInterval(() => {
|
||||
if (os.platform() === "linux") {
|
||||
return setLinuxSystemInfo();
|
||||
}
|
||||
if (os.platform() === "win32") {
|
||||
return setWindowsSystemInfo();
|
||||
}
|
||||
return otherSystemInfo();
|
||||
}, 3000);
|
||||
|
||||
function otherSystemInfo() {
|
||||
info.freemem = os.freemem();
|
||||
info.totalmem = os.totalmem();
|
||||
info.memUsage = (os.totalmem() - os.freemem()) / os.totalmem();
|
||||
osUtils.cpuUsage((p) => (info.cpuUsage = p));
|
||||
}
|
||||
|
||||
function setWindowsSystemInfo() {
|
||||
info.freemem = os.freemem();
|
||||
info.totalmem = os.totalmem();
|
||||
info.memUsage = (os.totalmem() - os.freemem()) / os.totalmem();
|
||||
osUtils.cpuUsage((p) => (info.cpuUsage = p));
|
||||
}
|
||||
|
||||
function setLinuxSystemInfo() {
|
||||
try {
|
||||
// read memory data based on /proc/meminfo
|
||||
const data = fs.readFileSync("/proc/meminfo", { encoding: "utf-8" });
|
||||
const list = data.split("\n");
|
||||
const infoTable: IInfoTable = {};
|
||||
list.forEach((line) => {
|
||||
const kv = line.split(":");
|
||||
if (kv.length === 2) {
|
||||
const k = kv[0].replace(/ /gim, "").replace(/\t/gim, "").trim().toLowerCase();
|
||||
let v = kv[1].replace(/ /gim, "").replace(/\t/gim, "").trim().toLowerCase();
|
||||
v = v.replace(/kb/gim, "").replace(/mb/gim, "").replace(/gb/gim, "");
|
||||
let vNumber = parseInt(v);
|
||||
if (isNaN(vNumber)) vNumber = 0;
|
||||
infoTable[k] = vNumber;
|
||||
}
|
||||
});
|
||||
const memAvailable = infoTable["memavailable"] ?? infoTable["memfree"];
|
||||
const memTotal = infoTable["memtotal"];
|
||||
info.freemem = memAvailable * 1024;
|
||||
info.totalmem = memTotal * 1024;
|
||||
info.memUsage = (info.totalmem - info.freemem) / info.totalmem;
|
||||
osUtils.cpuUsage((p) => (info.cpuUsage = p));
|
||||
} catch (error) {
|
||||
// If the reading is wrong, the default general reading method is automatically used
|
||||
otherSystemInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export function systemInfo() {
|
||||
return info;
|
||||
}
|
107
common/src/system_storage.ts
Executable file
107
common/src/system_storage.ts
Executable file
@ -0,0 +1,107 @@
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
|
||||
export default class StorageSubsystem {
|
||||
public static readonly DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
|
||||
public static readonly INDEX_PATH = path.normalize(path.join(process.cwd(), "data", "index"));
|
||||
|
||||
private checkFileName(name: string) {
|
||||
const blackList = ["\\", "/", ".."];
|
||||
for (const ch of blackList) {
|
||||
if (name.includes(ch)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public writeFile(name: string, data: string) {
|
||||
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
|
||||
fs.writeFileSync(targetPath, data, { encoding: "utf-8" });
|
||||
}
|
||||
|
||||
public readFile(name: string) {
|
||||
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
|
||||
return fs.readFileSync(targetPath, { encoding: "utf-8" });
|
||||
}
|
||||
|
||||
public deleteFile(name: string) {
|
||||
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
|
||||
fs.removeSync(targetPath);
|
||||
}
|
||||
|
||||
public fileExists(name: string) {
|
||||
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
|
||||
return fs.existsSync(targetPath);
|
||||
}
|
||||
|
||||
// Stored in local file based on class definition and identifier
|
||||
public store(category: string, uuid: string, object: any) {
|
||||
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
|
||||
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
|
||||
if (!this.checkFileName(uuid))
|
||||
throw new Error(`UUID ${uuid} does not conform to specification`);
|
||||
const filePath = path.join(dirPath, `${uuid}.json`);
|
||||
const data = JSON.stringify(object, null, 4);
|
||||
fs.writeFileSync(filePath, data, { encoding: "utf-8" });
|
||||
}
|
||||
|
||||
// deep copy of the primitive type with the copy target as the prototype
|
||||
protected defineAttr(target: any, object: any): any {
|
||||
for (const v of Object.keys(target)) {
|
||||
const objectValue = object[v];
|
||||
if (objectValue === undefined) continue;
|
||||
if (objectValue instanceof Array) {
|
||||
target[v] = objectValue;
|
||||
continue;
|
||||
}
|
||||
if (objectValue instanceof Object && typeof objectValue === "object") {
|
||||
this.defineAttr(target[v], objectValue);
|
||||
continue;
|
||||
}
|
||||
target[v] = objectValue;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate an object based on the class definition and identifier
|
||||
*/
|
||||
public load(category: string, classz: any, uuid: string) {
|
||||
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
|
||||
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
|
||||
if (!this.checkFileName(uuid))
|
||||
throw new Error(`UUID ${uuid} does not conform to specification`);
|
||||
const filePath = path.join(dirPath, `${uuid}.json`);
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
const data = fs.readFileSync(filePath, { encoding: "utf-8" });
|
||||
const dataObject = JSON.parse(data);
|
||||
const target = new classz();
|
||||
// for (const v of Object. keys(target)) {
|
||||
// if (dataObject[v] !== undefined) target[v] = dataObject[v];
|
||||
// }
|
||||
// deep object copy
|
||||
return this.defineAttr(target, dataObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all identifiers related to this class through the class definition
|
||||
*/
|
||||
public list(category: string) {
|
||||
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
|
||||
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
|
||||
const files = fs.readdirSync(dirPath);
|
||||
const result = new Array<string>();
|
||||
files.forEach((name) => {
|
||||
result.push(name.replace(path.extname(name), ""));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an identifier instance of the specified type through the class definition
|
||||
*/
|
||||
public delete(category: string, uuid: string) {
|
||||
const filePath = path.join(StorageSubsystem.DATA_PATH, category, `${uuid}.json`);
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
fs.removeSync(filePath);
|
||||
}
|
||||
}
|
75
common/src/typecheck.ts
Executable file
75
common/src/typecheck.ts
Executable file
@ -0,0 +1,75 @@
|
||||
export function configureEntityParams(self: any, args: any, key: string, typeFn?: Function): any {
|
||||
const selfDefaultValue = self[key] ?? null;
|
||||
const v = args[key] != null ? args[key] : selfDefaultValue;
|
||||
|
||||
if (typeFn === Number) {
|
||||
if (v === "" || v == null) {
|
||||
self[key] = null;
|
||||
} else {
|
||||
if (isNaN(Number(v)))
|
||||
throw new Error(
|
||||
`ConfigureEntityParams Error: Expected type to be Number, but got ${typeof v}`
|
||||
);
|
||||
self[key] = Number(v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeFn === String) {
|
||||
if (v == null) {
|
||||
self[key] = null;
|
||||
} else {
|
||||
self[key] = String(v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeFn === Boolean) {
|
||||
if (v == null) {
|
||||
self[key] = false;
|
||||
} else {
|
||||
self[key] = Boolean(v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeFn === Array) {
|
||||
if (v == null) return (self[key] = null);
|
||||
if (!(v instanceof Array))
|
||||
throw new Error(
|
||||
`ConfigureEntityParams Error: Expected type to be Array, but got ${typeof v}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeFn) {
|
||||
self[key] = typeFn(v);
|
||||
} else {
|
||||
self[key] = v;
|
||||
}
|
||||
}
|
||||
|
||||
export function toText(v: any) {
|
||||
if (isEmpty(v)) return null;
|
||||
return String(v);
|
||||
}
|
||||
|
||||
export function toNumber(v: any) {
|
||||
if (isEmpty(v)) return null;
|
||||
if (isNaN(Number(v))) return null;
|
||||
return Number(v);
|
||||
}
|
||||
|
||||
export function toBoolean(v: any) {
|
||||
if (isEmpty(v)) return null;
|
||||
return Boolean(v);
|
||||
}
|
||||
|
||||
export function isEmpty(v: any) {
|
||||
return v === null || v === undefined;
|
||||
}
|
||||
|
||||
export function supposeValue(v: any, def: any = null) {
|
||||
if (isEmpty(v)) return def;
|
||||
return v;
|
||||
}
|
20
common/tsconfig.json
Executable file
20
common/tsconfig.json
Executable file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2018",
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": ["node_modules/*", "src/types/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
42
daemon/package-lock.json
generated
42
daemon/package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@koa/router": "^10.0.0",
|
||||
"archiver": "^5.3.1",
|
||||
"axios": "^1.1.3",
|
||||
"common": "file:../common/dist",
|
||||
"compressing": "^1.5.1",
|
||||
"crypto": "^1.0.1",
|
||||
"dockerode": "3.1.0",
|
||||
@ -23,6 +24,7 @@
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-send": "^5.0.1",
|
||||
"log4js": "^6.4.0",
|
||||
"mcsmanager-common": "file:../common/dist",
|
||||
"module-alias": "^2.2.3",
|
||||
"node-disk-info": "^1.3.0",
|
||||
"node-schedule": "^2.0.0",
|
||||
@ -61,6 +63,22 @@
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"../common/dist": {
|
||||
"name": "common",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"fs-extra": "^11.1.1"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"extraneous": true
|
||||
},
|
||||
"common/dist": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
|
||||
@ -1498,6 +1516,10 @@
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/common": {
|
||||
"resolved": "../common/dist",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/compress-commons": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz",
|
||||
@ -3230,6 +3252,10 @@
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mcsmanager-common": {
|
||||
"resolved": "../common/dist",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@ -6317,6 +6343,14 @@
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"common": {
|
||||
"version": "file:../common/dist",
|
||||
"requires": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"fs-extra": "^11.1.1"
|
||||
}
|
||||
},
|
||||
"compress-commons": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz",
|
||||
@ -7640,6 +7674,14 @@
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"mcsmanager-common": {
|
||||
"version": "file:../common/dist",
|
||||
"requires": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^20.9.0",
|
||||
"fs-extra": "^11.1.1"
|
||||
}
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"@koa/router": "^10.0.0",
|
||||
"archiver": "^5.3.1",
|
||||
"axios": "^1.1.3",
|
||||
"common": "file:../common/dist",
|
||||
"compressing": "^1.5.1",
|
||||
"crypto": "^1.0.1",
|
||||
"dockerode": "3.1.0",
|
||||
@ -31,6 +32,7 @@
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-send": "^5.0.1",
|
||||
"log4js": "^6.4.0",
|
||||
"mcsmanager-common": "file:../common/dist",
|
||||
"module-alias": "^2.2.3",
|
||||
"node-disk-info": "^1.3.0",
|
||||
"node-schedule": "^2.0.0",
|
||||
|
@ -8,6 +8,10 @@ import { Server, Socket } from "socket.io";
|
||||
import { LOCAL_PRESET_LANG_PATH } from "./const";
|
||||
import logger from "./service/log";
|
||||
|
||||
import { StorageSubsystem } from "common";
|
||||
const a = new StorageSubsystem();
|
||||
console.log("XZXZXZX:", a);
|
||||
|
||||
initVersionManager();
|
||||
const VERSION = getVersion();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user