Refactor: common code

This commit is contained in:
unitwk 2023-11-15 20:38:08 +08:00
parent a0b8944f13
commit 0b6d9950e3
20 changed files with 51 additions and 413 deletions

View File

@ -6,7 +6,7 @@ import child_process from "child_process";
import os from "os";
import archiver from "archiver";
import StreamZip, { async } from "node-stream-zip";
import { processWrapper } from "./process_tools";
import { processWrapper } from "common";
import { PTY_PATH } from "../const";
// const StreamZip = require('node-stream-zip');

View File

@ -1,116 +0,0 @@
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);
}
}

View File

@ -1,99 +0,0 @@
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;
}

View File

@ -1,109 +1,2 @@
import path from "path";
import fs from "fs-extra";
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);
}
}
import { StorageSubsystem } from "common";
export default new StorageSubsystem();

View File

@ -1,75 +0,0 @@
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;
}

View File

@ -8,7 +8,7 @@ import EventEmitter from "events";
import { IInstanceProcess } from "../../instance/interface";
import { ChildProcess, exec, spawn } from "child_process";
import { commandStringToArray } from "../base/command_parser";
import { killProcess } from "../../../common/process_tools";
import { killProcess } from "common";
// Error exception at startup
class StartupError extends Error {

View File

@ -1,5 +1,5 @@
import { $t } from "../../../i18n";
import { killProcess } from "../../../common/process_tools";
import { killProcess } from "common";
import { ChildProcess, exec, spawn } from "child_process";
import logger from "../../../service/log";
import Instance from "../../instance/instance";

View File

@ -10,7 +10,7 @@ import EventEmitter from "events";
import { IInstanceProcess } from "../../instance/interface";
import { ChildProcess, ChildProcessWithoutNullStreams, exec, spawn } from "child_process";
import { commandStringToArray } from "../base/command_parser";
import { killProcess } from "../../../common/process_tools";
import { killProcess } from "common";
import GeneralStartCommand from "../general/general_start";
import FunctionDispatcher from "../dispatcher";
import StartCommand from "../start";

View File

@ -3,13 +3,13 @@ import fs from "fs-extra";
import path from "path";
import { spawn, ChildProcess } from "child_process";
import os from "os";
import { killProcess } from "../../../common/process_tools";
import { killProcess } from "common";
import { ILifeCycleTask } from "../../instance/life_cycle";
import Instance from "../../instance/instance";
import KillCommand from "../kill";
import logger from "../../../service/log";
import { $t } from "../../../i18n";
import { processWrapper } from "../../../common/process_tools";
import { processWrapper } from "common";
import { FRPC_PATH } from "../../../const";
import { downloadFileToLocalFile } from "../../../service/download";
export class OpenFrp {

View File

@ -12,7 +12,7 @@ import { PresetCommandManager } from "./preset";
import FunctionDispatcher from "../commands/dispatcher";
import { IInstanceProcess } from "./interface";
import StartCommand from "../commands/start";
import { configureEntityParams } from "../../common/typecheck";
import { configureEntityParams } from "common";
import { PTY_PATH } from "../../const";
import { OpenFrp } from "../commands/task/openfrp";

View File

@ -12,7 +12,7 @@ import StopCommand from "../entity/commands/stop";
import SendCommand from "../entity/commands/cmd";
import KillCommand from "../entity/commands/kill";
import { IInstanceDetail, IJson } from "../service/interfaces";
import { QueryMapWrapper } from "../common/query_wrapper";
import { QueryMapWrapper } from "common";
import ProcessInfoCommand from "../entity/commands/process_info";
import FileManager from "../service/system_file";
import { ProcessConfig } from "../entity/instance/process_config";

View File

@ -3,7 +3,7 @@ import { routerApp } from "../service/router";
import InstanceSubsystem from "../service/system_instance";
import Instance from "../entity/instance/instance";
import { systemInfo } from "../common/system_info";
import { systemInfo } from "common";
import { getVersion } from "../service/version";
import { globalConfiguration } from "../entity/config";
import i18next from "i18next";

View File

@ -1,7 +1,7 @@
import fs from "fs-extra";
import * as log4js from "log4js";
import os from "os";
import { systemInfo } from "../common/system_info";
import { systemInfo } from "common";
import { $t } from "../i18n";
const LOG_FILE_PATH = "logs/current.log";

View File

@ -13,7 +13,7 @@ import { Socket } from "socket.io";
import StorageSubsystem from "../common/system_storage";
import InstanceConfig from "../entity/instance/Instance_config";
import InstanceStreamListener from "../common/instance_stream";
import { QueryMapWrapper } from "../common/query_wrapper";
import { QueryMapWrapper } from "common";
import FunctionDispatcher from "../entity/commands/dispatcher";
import InstanceControl from "./system_instance_control";
import StartCommand from "../entity/commands/start";

View File

@ -1,6 +1,6 @@
// Copyright (C) 2023 MCSManager <mcsmanager-dev@outlook.com>
import { systemInfo } from "../common/system_info";
import { systemInfo } from "common";
// Visual data subsystem: responsible for collecting system data and event data, and providing some methods to display them
class LineQueue<T> {

View File

@ -12,6 +12,7 @@
"@koa/router": "^10.0.0",
"axios": "^0.27.2",
"bcryptjs": "^2.4.3",
"common": "file:../common/dist",
"crypto": "^1.0.1",
"i18n": "^0.15.0",
"i18next": "^21.8.14",
@ -52,6 +53,21 @@
"webpack-node-externals": "^3.0.0"
}
},
"../common/dist": {
"version": "1.0.0",
"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"
}
},
"node_modules/@babel/code-frame": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
@ -1429,6 +1445,10 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/common": {
"resolved": "../common/dist",
"link": true
},
"node_modules/component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@ -5870,6 +5890,20 @@
"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",
"@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"
}
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",

View File

@ -21,6 +21,7 @@
"@koa/router": "^10.0.0",
"axios": "^0.27.2",
"bcryptjs": "^2.4.3",
"common": "file:../common/dist",
"crypto": "^1.0.1",
"i18n": "^0.15.0",
"i18next": "^21.8.14",

View File

@ -5,7 +5,7 @@ import RemoteServiceSubsystem from "../../service/system_remote_service";
import VisualDataSubsystem from "../../service/system_visual_data";
import RemoteRequest from "../../service/remote_command";
import os from "os";
import { systemInfo } from "../../common/system_info";
import { systemInfo } from "common";
import { getVersion, specifiedDaemonVersion } from "../../version";
import GlobalVariable from "../../common/global_variable";
import {

View File

@ -4,7 +4,7 @@ import { IUserApp, User, UserPassWordType } from "../entity/user";
import { logger } from "./log";
import { IUser } from "../entity/entity_interface";
import Storage from "../common/storage/sys_storage";
import { QueryWrapper, LocalFileSource } from "../common/query_wrapper";
import { QueryWrapper, LocalFileSource } from "common";
import { $t } from "../i18n";
import bcrypt from "bcryptjs";

View File

@ -1,5 +1,5 @@
import { logger } from "./log";
import { systemInfo } from "../common/system_info";
import { systemInfo } from "common";
import RemoteServiceSubsystem from "../service/system_remote_service";
import RemoteRequest from "./remote_command";