You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/hydrooj/src/utils.ts

257 lines
7.9 KiB
TypeScript

import fs from 'fs';
import { Duplex } from 'stream';
import cluster from 'cluster';
import path from 'path';
import serialize from 'serialize-javascript';
import Lru from 'lru-cache';
import { ObjectID } from 'mongodb';
import { isMoment } from 'moment';
import { Moment } from 'moment-timezone';
declare global {
interface StringConstructor {
random: (digit?: number) => string;
}
interface ArrayConstructor {
isDiff: (a: any[], b: any[]) => boolean;
}
interface Date {
format: (fmt?: string) => string;
}
interface Math {
sum: (...args: Array<number[] | number>) => number;
}
interface SetConstructor {
isSuperset: (set: Set<any>, subset: Set<any>) => boolean;
intersection: <T>(setA: Set<T>, setB: Set<T>) => Set<T>;
union: <T>(setA: Set<T>, setB: Set<T>) => Set<T>;
}
}
4 years ago
const lru = new Lru(1000);
export function lrucache(target: any, funcName: string, obj: any) {
const originalMethod = obj.value;
obj.value = function func(...args: any[]) {
const key = serialize({ funcName, args });
let val = lru.get(key);
if (val) return val;
val = originalMethod.call(this, ...args);
lru.set(key, val);
return val;
};
return obj;
}
if (!cluster.worker) {
// @ts-ignore
cluster.worker = { id: 0 };
}
4 years ago
const dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
String.random = function random(digit = 32) {
let str = '';
4 years ago
for (let i = 1; i <= digit; i++) str += dict[Math.floor(Math.random() * 62)];
return str;
};
5 years ago
Array.isDiff = function isDiff(a, b) {
5 years ago
if (a.length !== b.length) return true;
5 years ago
a.sort();
b.sort();
for (const i in a) {
if (a[i] !== b[i]) return true;
}
5 years ago
return false;
};
5 years ago
Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') {
let m = this.getMonth() + 1;
if (m < 10) m = `0${m}`;
let d = this.getDate();
if (d < 10) d = `0${d}`;
let H = this.getHours();
if (H < 10) H = `0${H}`;
let M = this.getMinutes();
if (M < 10) M = `0${M}`;
let S = this.getSeconds();
if (S < 10) S = `0${S}`;
return fmt
.replace('%Y', this.getFullYear())
.replace('%m', m)
.replace('%d', d)
.replace('%H', H)
.replace('%M', M)
.replace('%S', S);
};
Math.sum = function sum(...args) {
let s = 0;
for (const i of args) {
if (i instanceof Array) {
for (const j of i) {
s += j;
}
} else s += i;
}
return s;
4 years ago
};
Set.isSuperset = function isSuperset(set, subset) {
for (const elem of subset) {
if (!set.has(elem)) return false;
}
return true;
};
4 years ago
Set.union = function Union<T>(setA: Set<T>, setB: Set<T>) {
4 years ago
const union = new Set(setA);
for (const elem of setB) union.add(elem);
return union;
};
4 years ago
Set.intersection = function Intersection<T>(setA: Set<T>, setB: Set<T>): Set<T> {
4 years ago
const intersection = new Set();
for (const elem of setB) {
4 years ago
if (setA.has(elem)) intersection.add(elem);
}
// @ts-ignore
4 years ago
return intersection;
};
export function folderSize(folderPath: string) {
let size = 0;
4 years ago
const _next = function a(p: string) {
if (p) {
const stats = fs.statSync(p);
if (!stats.isDirectory() || stats.isSymbolicLink()) {
if (!stats.isSymbolicLink()) size += stats.size;
} else {
size += stats.size;
const files = fs.readdirSync(p);
if (Array.isArray(files)) {
files.forEach((file) => {
_next(path.join(p, file));
});
}
}
}
};
_next(folderPath);
return size;
}
4 years ago
const TIME_RE = /^([0-9]+(?:\.[0-9]*)?)([mu]?)s?$/i;
const TIME_UNITS = { '': 1000, m: 1, u: 0.001 };
const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i;
const MEMORY_UNITS = { k: 0.1, m: 1, g: 1024 };
export function parseTimeMS(str: string) {
4 years ago
const match = TIME_RE.exec(str);
if (!match) throw new Error(`${str} error parsing time`);
return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2]]);
4 years ago
}
export function parseMemoryMB(str: string) {
4 years ago
const match = MEMORY_RE.exec(str);
if (!match) throw new Error(`${str} error parsing memory`);
return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2]]);
4 years ago
}
export function isClass(obj: any, strict = false) {
if (typeof obj !== 'function') return false;
const str = obj.toString();
if (obj.prototype === undefined) return false;
if (obj.prototype.constructor !== obj) return false;
if (str.slice(0, 5) === 'class') return true;
if (Object.getOwnPropertyNames(obj.prototype).length >= 2) return true;
if (/^function\s+\(|^function\s+anonymous\(/.test(str)) return false;
if (strict && /^function\s+[A-Z]/.test(str)) return true;
if (/\b\(this\b|\bthis[.[]\b/.test(str)) {
if (!strict || /classCallCheck\(this/.test(str)) return true;
return /^function\sdefault_\d+\s*\(/.test(str);
}
return false;
}
export function streamToBuffer(stream): Promise<Buffer> {
return new Promise((resolve, reject) => {
const buffers = [];
stream.on('error', reject);
stream.on('data', (data) => buffers.push(data));
stream.on('end', () => resolve(Buffer.concat(buffers)));
});
}
export function bufferToStream(buffer: Buffer): NodeJS.ReadableStream {
const stream = new Duplex();
stream.push(buffer);
stream.push(null);
return stream;
}
export function buildProjection(fields: string[]): Record<string, 1> {
const o = {};
for (const k of fields) o[k] = 1;
return o;
}
export function sleep(timeout: number) {
return new Promise((resolve) => {
4 years ago
setTimeout(resolve, timeout);
});
}
function deepen(modifyString: (source: string) => string) {
function modifyObject<T>(source: T): T {
if (typeof source !== 'object' || !source) return source;
if (Array.isArray(source)) return source.map(modifyObject) as any;
const result = {} as T;
for (const key in source) {
result[modifyString(key)] = modifyObject(source[key]);
}
return result;
}
return function t<T>(source: T): T {
4 years ago
if (typeof source === 'string') return modifyString(source) as any;
return modifyObject(source);
};
}
export const camelCase = deepen((source) => source.replace(/[_-][a-z]/g, (str) => str.slice(1).toUpperCase()));
export const paramCase = deepen((source) => source.replace(/_/g, '-').replace(/(?<!^)[A-Z]/g, (str) => `-${str.toLowerCase()}`));
export const snakeCase = deepen((source) => source.replace(/-/g, '_').replace(/(?<!^)[A-Z]/g, (str) => `_${str.toLowerCase()}`));
4 years ago
export namespace Time {
export const second = 1000;
export const minute = second * 60;
export const hour = minute * 60;
export const day = hour * 24;
export const week = day * 7;
export function formatTimeShort(ms: number) {
const abs = Math.abs(ms);
if (abs >= day - hour / 2) {
return `${Math.round(ms / day)}d`;
} if (abs >= hour - minute / 2) {
return `${Math.round(ms / hour)}h`;
} if (abs >= minute - second / 2) {
return `${Math.round(ms / minute)}m`;
} if (abs >= second) {
return `${Math.round(ms / second)}s`;
}
return `${ms}ms`;
}
export function getObjectID(timestamp: string | Date | Moment) {
let _timestamp: number;
if (typeof timestamp === 'string') _timestamp = new Date(timestamp).getTime();
else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime();
else _timestamp = timestamp.getTime();
const hexSeconds = Math.floor(_timestamp / 1000).toString(16);
const constructedObjectId = new ObjectID(`${hexSeconds}0000000000000000`);
return constructedObjectId;
}
4 years ago
}