文件结构更改

This commit is contained in:
undefined committed 2020-08-04 13:42:42 +08:00
1 parent b3af28dc43
commit 1f1d12ad73
109 files changed
+544 -3296

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
import superagent from 'superagent';
import fs from 'fs';
async function _download(url: string, path: string, retry: number) {
const w = fs.createWriteStream(path);
superagent.get(url).retry(retry).pipe(w);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
});
return path;
}
export default function download(url: string, path?: string, retry = 3) {
if (path) return _download(url, path, retry);
return superagent.get(url).retry(retry);
}
global.Hydro.lib.download = download;
+7
View File
@@ -0,0 +1,7 @@
import crypto from 'crypto';
export default function hash(password: string, salt: string) {
return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256').toString('hex').substr(0, 64);
}
global.Hydro.lib['hash.hydro'] = hash;
+56
View File
@@ -0,0 +1,56 @@
const locales = {};
declare global {
interface String {
format: (...args: Array<any>) => string;
rawformat: (object: any) => string;
translate: (...languages: string[]) => string;
}
}
String.prototype.format = function formatStr(...args) {
let result = this;
if (args.length > 0) {
if (args.length === 1 && typeof (args[0]) === 'object') {
for (const key in args) {
if (args[key] !== undefined) {
const reg = new RegExp(`(\\{${key}\\})`, 'g');
result = result.replace(reg, args[key]);
}
}
} else {
for (let i = 0; i < args.length; i++) {
if (args[i] !== undefined) {
const reg = new RegExp(`(\\{)${i}(\\})`, 'g');
result = result.replace(reg, args[i]);
}
}
}
}
return result;
};
String.prototype.rawformat = function rawFormat(object) {
const res = this.split('{@}');
return [res[0], object, res[1]].join();
};
String.prototype.translate = function translate(...languages: string[]) {
if (languages.length === 0) languages = ['zh_CN'];
for (const language of languages) {
if (locales[language]) {
if (locales[language][this] !== undefined) return locales[language][this];
}
}
return this;
};
export default function load(data) {
for (const i in data) {
if (!locales[i]) locales[i] = data[i];
else locales[i] = Object.assign(locales[i], data[i]);
}
}
global.Hydro.locales = locales;
global.Hydro.lib.i18n = load;
+71
View File
@@ -0,0 +1,71 @@
import assert from 'assert';
import superagent from 'superagent';
import download from './download';
import { ValidationError, RemoteOnlineJudgeError } from '../error';
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
export async function syzoj(url: string, handler: any) {
assert(url.match(RE_SYZOJ), new ValidationError('url'));
if (!url.endsWith('/')) url += '/';
const [, host, pid] = RE_SYZOJ.exec(url);
const res = await superagent.get(`${url}export`);
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
assert(res.body.success, new RemoteOnlineJudgeError((res.body.error || {}).message));
const p = res.body.obj;
const content = [];
if (p.description) {
content.push(
handler.translate('problem.import.problem_description'),
p.description,
);
}
if (p.input_format) {
content.push(
handler.translate('problem.import.input_format'),
p.input_format,
);
}
if (p.output_format) {
content.push(
handler.translate('problem.import.output_format'),
p.output_format,
);
}
if (p.hint) {
content.push(
handler.translate('problem.import.hint'),
p.hint,
);
}
if (p.limit_and_hint) {
content.push(
handler.translate('problem.import.limit_and_hint'),
p.limit_and_hint,
);
}
if (p.have_additional_file) {
content.push(
handler.translate('problem.import.additional_file'),
`${url}download/additional_file`,
);
}
const pdoc = {
title: p.title,
content: content.join(' \n'),
owner: handler.user._id,
from: url,
pid: `${host}_${pid}`,
tags: p.tags,
config: {
time: p.time_limit,
memory: p.memory_limit * 1024,
filename: p.file_io_input_name,
type: p.type,
},
};
const r = download(`${url}testdata/download`);
return [pdoc, r];
}
global.Hydro.lib['import.syzoj'] = syzoj;
+35
View File
@@ -0,0 +1,35 @@
function decodeBase64WithUriEncoding(encodedText: string) {
return Buffer.from(encodedText, 'base64').toString('utf8');
}
function unescapedString(escapedString: string) {
escapedString += new Array(5 - (escapedString.length % 4)).join('=');
return escapedString.replace(/-/g, '+').replace(/_/g, '/');
}
function decodeJWT(idToken: string) {
const token = idToken.split('.');
if (token.length !== 3) {
throw new Error('Invalid idToken');
}
try {
const headerSegment = JSON.parse(decodeBase64WithUriEncoding(token[0]));
const payloadSegment = JSON.parse(decodeBase64WithUriEncoding(token[1]));
const signature = unescapedString(token[2]);
return {
dataToSign: [token[0], token[1]].join('.'),
header: headerSegment,
payload: payloadSegment,
signature,
};
} catch (e) {
throw new Error('Invalid payload');
}
}
export function decode(idToken: string) {
const decodedJWT = decodeJWT(idToken);
return decodedJWT.payload;
}
global.Hydro.lib.jwt = { decode };
+41
View File
@@ -0,0 +1,41 @@
import serialize from 'serialize-javascript';
function wrap(func: Function, type: string) {
return (...args: any) => {
const time = new Date();
func(...args);
if (global.Hydro.model.message) {
try {
global.Hydro.model.message.send(1, 1, serialize({ time, type, args }), 0);
} catch (e) {
func(e.message);
}
}
};
}
class Logger {
log: (...args: any[]) => void;
error: (...args: any[]) => void;
info: (...args: any[]) => void;
warn: (...args: any[]) => void;
debug: (...args: any[]) => void;
constructor() {
this.log = wrap(console.log, 'log');
this.error = wrap(console.error, 'error');
this.info = wrap(console.info, 'info');
this.warn = wrap(console.warn, 'warn');
this.debug = wrap(console.debug, 'debug');
}
}
const exp = new Logger();
global.Hydro.lib.logger = exp;
export default exp;
+39
View File
@@ -0,0 +1,39 @@
import nodemailer from 'nodemailer';
import { SendMailError } from '../error';
export async function sendMail(to: string, subject: string, text: string, html: string) {
let t: unknown;
try {
const { system } = global.Hydro.model;
const [host, port, secure, user, pass, from] = await Promise.all([
system.get('smtp.host'),
system.get('smtp.port'),
system.get('smtp.secure'),
system.get('smtp.user'),
system.get('smtp.pass'),
system.get('smtp.from'),
]);
const transporter = nodemailer.createTransport({
host, port, secure, auth: { user, pass },
});
t = await new Promise((resolve, reject) => {
transporter.sendMail({
from,
to,
subject,
text,
html,
}, (err, info) => {
if (err) reject(err);
else resolve(info);
});
});
} catch (e) {
throw new SendMailError(to);
}
return t;
}
global.Hydro.lib.mail = {
sendMail,
};
+137
View File
@@ -0,0 +1,137 @@
// From https://github.com/pvorb/node-md5/blob/master/md5.js
// BSD 3-Clause "New" or "Revised" License
/* eslint-disable max-len */
function safeAdd(x, y) {
const lsw = (x & 0xffff) + (y & 0xffff);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
const bitRotateLeft = (num, cnt) => (num << cnt) | (num >>> (32 - cnt));
const md5cmn = (q, a, b, x, s, t) => safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
const md5ff = (a, b, c, d, x, s, t) => md5cmn((b & c) | (~b & d), a, b, x, s, t);
const md5gg = (a, b, c, d, x, s, t) => md5cmn((b & d) | (c & ~d), a, b, x, s, t);
const md5hh = (a, b, c, d, x, s, t) => md5cmn(b ^ c ^ d, a, b, x, s, t);
const md5ii = (a, b, c, d, x, s, t) => md5cmn(c ^ (b | ~d), a, b, x, s, t);
function binlMD5(x, len) {
x[len >> 5] |= 0x80 << len % 32;
x[(((len + 64) >>> 9) << 4) + 14] = len;
let i;
let olda;
let oldb;
let oldc;
let oldd;
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
for (i = 0; i < x.length; i += 16) {
olda = a;
oldb = b;
oldc = c;
oldd = d;
a = md5ff(a, b, c, d, x[i], 7, -680876936);
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5gg(b, c, d, a, x[i], 20, -373897302);
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5hh(d, a, b, c, x[i], 11, -358537222);
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5ii(a, b, c, d, x[i], 6, -198630844);
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safeAdd(a, olda);
b = safeAdd(b, oldb);
c = safeAdd(c, oldc);
d = safeAdd(d, oldd);
}
return [a, b, c, d];
}
function binl2rstr(input) {
let i;
let output = '';
const length32 = input.length * 32;
for (i = 0; i < length32; i += 8) { output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); }
return output;
}
function rstr2binl(input) {
let i;
const output = [];
output[(input.length >> 2) - 1] = undefined;
for (i = 0; i < output.length; i += 1) { output[i] = 0; }
const length8 = input.length * 8;
for (i = 0; i < length8; i += 8) output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
return output;
}
const rstrMD5 = (s) => binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
function rstr2hex(input) {
const hexTab = '0123456789abcdef';
let output = '';
let x;
let i;
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i);
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
}
return output;
}
export default function md5(s: string) {
return rstr2hex(rstrMD5(unescape(encodeURIComponent(s))));
}
global.Hydro.lib.md5 = md5;
+78
View File
@@ -0,0 +1,78 @@
import AnsiUp from 'ansi_up';
import { ObjectID } from 'mongodb';
import md5 from './md5';
const AU = new AnsiUp();
export function ansiToHtml(str: string) {
return AU.ansi_to_html(str);
}
export function gravatar(email: string, s = 32) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`;
}
export function datetimeSpan(dt: Date | ObjectID, relative = true, format = '%Y-%m-%d %H:%M:%S') {
if (!dt) return 'DATETIME_SPAN_ERROR';
if (dt instanceof ObjectID) dt = new Date(dt.generationTime * 1000);
else if (typeof dt === 'number' || typeof dt === 'string') dt = new Date(dt);
return '<span class="time{0}" data-timestamp="{1}">{2}</span>'.format(
relative ? ' relative' : '',
dt.getTime() / 1000,
dt.format(format),
);
}
export function* paginate(page: number, numPages: number) {
const radius = 2;
let first: number;
let last: number;
if (page > 1) {
if (page > 2) yield ['first', 1];
yield ['previous', page - 1];
}
if (page <= radius) [first, last] = [1, Math.min(1 + radius * 2, numPages)];
else if (page >= numPages - radius) {
[first, last] = [Math.max(1, numPages - radius * 2), numPages];
} else {
[first, last] = [page - radius, page + radius];
}
if (first > 1) yield ['ellipsis', 0];
for (let page0 = first; page0 < last + 1; page0++) {
if (page0 !== page) yield ['page', page0];
else yield ['current', page];
}
if (last < numPages) yield ['ellipsis', 0];
if (page < numPages) {
yield ['next', page + 1];
if (page < numPages - 1) yield ['last', numPages];
}
}
export function size(s: number, base = 1) {
s *= base;
const unit = 1024;
const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
for (const unitName of unitNames) {
if (s < unit) return '{0} {1}'.format(Math.round(s * 10) / 10, unitName);
s /= unit;
}
return `${Math.round(s * unit)} ${unitNames[unitNames.length - 1]}`;
}
function _digit2(number: number) {
return number < 10 ? `0${number}` : number.toString();
}
export function formatSeconds(_seconds = '0') {
const seconds = parseInt(_seconds, 10);
return '{0}:{1}:{2}'.format(
_digit2(Math.floor(seconds / 3600)),
_digit2(Math.floor((seconds % 3600) / 60)),
_digit2(seconds % 60),
);
}
global.Hydro.lib.misc = {
gravatar, datetimeSpan, paginate, size, formatSeconds, ansiToHtml,
};
+57
View File
@@ -0,0 +1,57 @@
import { isNull } from 'lodash';
import { PERM, PRIV } from '../model/builtin';
global.Hydro.ui.nav = [];
const trueChecker = () => true;
const Checker = (perm: bigint, priv: number, checker: Function = trueChecker) => (handler) => (
checker(handler)
&& (perm ? handler.user.hasPerm(perm) : true)
&& (priv ? handler.user.hasPriv(priv) : true)
);
const Item = (
name: string, args: any, prefix: string,
...permPrivChecker: Array<number | bigint | Function>
) => {
let _priv: number;
let _perm: bigint;
let checker: Function = trueChecker;
for (const item of permPrivChecker) {
if (item instanceof Object && !isNull(item)) {
if (item instanceof Array) {
if (typeof item[0] === 'number') {
// @ts-ignore
_priv = item;
} else if (typeof item[0] === 'bigint') {
// @ts-ignore
_perm = item;
}
} else if (typeof item.call !== 'undefined') {
checker = item;
}
} else if (typeof item === 'number') {
_priv = item;
} else if (typeof item === 'bigint') {
_perm = item;
}
}
checker = Checker(_perm, _priv, checker);
global.Hydro.ui.nav.push({
name, args: args || {}, prefix, checker,
});
};
Item('homepage', null, 'homepage');
Item('problem_main', null, 'problem', PERM.PERM_VIEW_PROBLEM);
Item('training_main', null, 'training', PERM.PERM_VIEW_TRAINING);
Item('homework_main', null, 'homework', PERM.PERM_VIEW_HOMEWORK);
Item('discussion_main', null, 'discussion', PERM.PERM_VIEW_DISCUSSION);
Item('contest_main', null, 'contest', PERM.PERM_VIEW_CONTEST);
Item('record_main', null, 'record');
Item('ranking', null, 'ranking', PERM.PERM_VIEW_RANKING);
Item('domain_dashboard', null, 'domain', PERM.PERM_EDIT_DOMAIN);
Item('manage_dashboard', null, 'manage', PRIV.PRIV_EDIT_SYSTEM);
global.Hydro.lib.nav = Item;
export default Item;
+16
View File
@@ -0,0 +1,16 @@
import { Cursor } from 'mongodb';
import { ValidationError } from '../error';
export default async function paginate(
cursor: Cursor, page: number, pageSize: number,
): Promise<[any[]/* docs */, number/* numPages */, number/* count */]> {
if (page <= 0) throw new ValidationError('page');
const [count, pageDocs] = await Promise.all([
cursor.count(),
cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(),
]);
const numPages = Math.floor((count + pageSize - 1) / pageSize);
return [pageDocs, numPages, count];
}
global.Hydro.lib.paginate = paginate;
+14
View File
@@ -0,0 +1,14 @@
function* ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): Generator<[number, T], void> {
let last = null;
let r = 0;
let count = 0;
for (const doc of diter) {
count++;
if (count === 1 || !equ(last, doc)) r = count;
last = doc;
yield [r, doc];
}
}
global.Hydro.lib.rank = ranked;
export default ranked;
+127
View File
@@ -0,0 +1,127 @@
// Based on https://github.com/QAQrz/Codeforces-Rating-System/blob/master/rating.py
class User {
uid: number;
rank: number;
old: number;
seed?: number;
delta?: number;
new?: number;
constructor(rank: number, old: number, uid = 0) {
this.rank = rank;
this.old = old;
this.seed = 1;
this.uid = uid;
}
}
class RatingCalculator {
users: User[];
constructor(users: User[]) {
this.users = [];
for (const user of users) {
this.users.push(new User(user.rank, user.old, user.uid));
}
}
// eslint-disable-next-line class-methods-use-this
calP(a: User, b: User) {
return 1 / (1 + 10 ** ((b.old - a.old) / 400));
}
getExSeed(rating: number, ownUser: User) {
const exUser = new User(0.0, rating);
let result = 1;
for (const user of this.users) {
if (user !== ownUser) result += this.calP(user, exUser);
}
return result;
}
calRating(rank: number, user: User) {
let left = 1;
let right = 8000;
while (right - left > 1) {
const mid = Math.floor((left + right) / 2);
if (this.getExSeed(mid, user) < rank) right = mid;
else left = mid;
}
return left;
}
calculate() {
// Calculate seed
for (let i = 0; i < this.users.length; i++) {
this.users[i].seed = 1;
for (let j = 0; j < this.users.length; j++) {
if (i !== j) {
this.users[i].seed += this.calP(this.users[j], this.users[i]);
}
}
}
// Calculate initial delta and sum_delta
let sumDelta = 0;
for (const user of this.users) {
user.delta = (this.calRating(Math.sqrt(user.rank * user.seed), user) - user.old) / 2;
sumDelta += user.delta;
}
// Calculate first inc
let inc = Math.floor(-sumDelta / this.users.length) - 1;
for (const user of this.users) user.delta += inc;
// Calculate second inc
this.users = this.users.sort((a, b) => b.old - a.old);
const s = Math.min(
this.users.length,
Math.floor(4 * Math.round(Math.sqrt(this.users.length))),
);
let sumS = 0;
for (let i = 0; i < s; i++) {
sumS += this.users[i].delta;
}
inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
// Calculate new rating
for (const user of this.users) {
user.delta += inc;
user.new = user.old + user.delta;
}
this.users = this.users.sort((a, b) => a.rank - b.rank);
return this.users;
}
}
interface RatingInputUser {
old: number,
uid: number,
rank: number,
}
interface RatingOutputUser {
new: number,
uid: number,
}
export default function calculate(users: RatingInputUser[]): RatingOutputUser[] {
let last_idx = 0;
let last_rank = 1;
for (let i = 1; i < users.length; i++) {
if (users[i].rank > last_rank) {
for (let j = last_idx; j < i; j++) users[j].rank = i;
last_idx = i;
last_rank = users[i].rank;
}
}
for (let i = last_idx; i < users.length; i++) {
users[i].rank = users.length;
}
const calculator = new RatingCalculator(users);
return calculator.calculate() as RatingOutputUser[];
}
global.Hydro.lib.rating = calculate;
+10
View File
@@ -0,0 +1,10 @@
import crypto from 'crypto';
function sha1(content: string) {
const hash = crypto.createHash('sha1');
hash.update(content);
return hash.digest('hex');
}
global.Hydro.lib.sha1 = sha1;
export default sha1;
+62
View File
@@ -0,0 +1,62 @@
import systeminformation from 'systeminformation';
import { size } from './misc';
const cache = {
mid: '', cpu: '', osinfo: '', flags: '',
};
export async function get() {
const [Cpu, Memory, OsInfo, CurrentLoad, CpuFlags, CpuTemp, Battery] = await Promise.all([
systeminformation.cpu(),
systeminformation.mem(),
systeminformation.osInfo(),
systeminformation.currentLoad(),
systeminformation.cpuFlags(),
systeminformation.cpuTemperature(),
systeminformation.battery(),
]);
const cpu = `${Cpu.manufacturer} ${Cpu.brand}`;
const memory = `${size(Memory.active)}/${size(Memory.total)}`;
const osinfo = `${OsInfo.distro} ${OsInfo.release} ${OsInfo.codename} ${OsInfo.kernel} ${OsInfo.arch}`;
const load = `${CurrentLoad.avgload}`;
const flags = CpuFlags;
let battery;
if (!Battery.hasbattery) battery = 'No battery';
else battery = `${Battery.type} ${Battery.model} ${Battery.percent}%${Battery.ischarging ? ' Charging' : ''}`;
const mid = OsInfo.serial;
cache.cpu = cpu;
cache.osinfo = osinfo;
cache.flags = flags;
cache.mid = mid;
return {
mid, cpu, memory, osinfo, load, flags, CpuTemp, battery,
};
}
export async function update(): Promise<[string, any, any]> {
const [Memory, CurrentLoad, CpuTemp, Battery] = await Promise.all([
systeminformation.mem(),
systeminformation.currentLoad(),
systeminformation.cpuTemperature(),
systeminformation.battery(),
]);
const {
mid, cpu, osinfo, flags,
} = cache;
const memory = `${size(Memory.active)}/${size(Memory.total)}`;
const load = `${CurrentLoad.avgload}`;
let battery;
if (!Battery.hasbattery) battery = 'No battery';
else battery = `${Battery.type} ${Battery.model} ${Battery.percent}%${Battery.ischarging ? ' Charging' : ''}`;
return [
mid,
{
memory, load, battery, CpuTemp,
},
{
mid, cpu, memory, osinfo, load, flags, battery, CpuTemp,
},
];
}
global.Hydro.lib.sysinfo = { get, update };
@@ -0,0 +1,32 @@
import yaml from 'js-yaml';
export default function convert(ini: string) {
const f = ini.split('\n');
const count = parseInt(f[0], 10);
const res = { cases: [] };
for (let i = 1; i <= count; i++) {
const [input, output, time, score, memory] = f[i].split('|');
const cur = {
input, output, score: parseInt(score, 10), time: `${time}s`, memory: '128m',
};
if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`;
res.cases.push(cur);
}
if (res.cases.length > 0) {
const { time, score, memory } = res.cases[0];
for (let i = 1; i < res.cases.length; i++) {
if (res.cases[i].time !== time
|| res.cases[i].score !== score
|| res.cases[i].memory !== memory) {
return yaml.safeDump(res);
}
}
}
return yaml.safeDump({
time: res.cases[0].time,
score: res.cases[0].score,
memory: res.cases[0].memory,
});
}
global.Hydro.lib['testdata.convert.ini'] = convert;
@@ -0,0 +1,60 @@
import Zip from 'adm-zip';
import { safeLoad } from 'js-yaml';
import { LocalProblemConfig } from '../interface';
export async function readConfig(filePath: string) {
const data = new Zip(filePath);
const entries = data.getEntries();
for (const entry of entries) {
if (entry.name.toLowerCase() === 'config.yaml') {
return entry.getData().toString();
}
if (entry.name.toLowerCase() === 'config.ini') {
const ini = entry.getData().toString();
const conv = global.Hydro.lib['testdata.convert.ini'];
return conv(ini);
}
}
return '';
}
interface ParseResult {
count: number,
memoryMax: number,
memoryMin: number,
timeMax: number,
timeMin: number,
}
export async function parseConfig(config: string | LocalProblemConfig) {
const result: ParseResult = {
count: 0,
memoryMin: Number.MAX_SAFE_INTEGER,
memoryMax: 0,
timeMin: Number.MAX_SAFE_INTEGER,
timeMax: 0,
};
let cfg: LocalProblemConfig = {};
if (typeof config === 'string') {
// TODO should validate here?
cfg = safeLoad(config) as LocalProblemConfig;
} else cfg = config;
if (cfg.cases) {
for (const c of cfg.cases) {
result.memoryMax = Math.max(result.memoryMax, c.memory);
result.memoryMin = Math.min(result.memoryMin, c.memory);
result.timeMax = Math.max(result.timeMax, c.time);
result.timeMin = Math.min(result.timeMin, c.time);
result.count++;
}
} else if (cfg.subtasks) {
for (const subtask of cfg.subtasks) {
result.memoryMax = Math.max(result.memoryMax, subtask.memory);
result.memoryMin = Math.min(result.memoryMin, subtask.memory);
result.timeMax = Math.max(result.timeMax, subtask.time);
result.timeMin = Math.min(result.timeMin, subtask.time);
}
}
}
global.Hydro.lib.testdataConfig = { readConfig, parseConfig };
+11
View File
@@ -0,0 +1,11 @@
import * as browser from 'detect-browser';
export function parse(str: string) {
return browser.parseUserAgent(str);
}
export function icon(str: string) {
return str.split(' ')[0].toLowerCase();
}
global.Hydro.lib.useragent = { parse, icon };
+157
View File
@@ -0,0 +1,157 @@
/* eslint-disable no-empty */
import { ValidationError } from '../error';
const RE_UID = /^-?\d+$/i;
const RE_PID = /^([a-zA-Z]+[a-zA-Z0-9]*)|$/i;
const RE_UNAME = /[^\s\u3000](.{,254}[^\s\u3000])?$/i;
const RE_ROLE = /^[_0-9A-Za-z]{1,256}$/i;
const RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
// eslint-disable-next-line consistent-return
function _validate(scheme: any, arg: any, base: string): void {
if (!(scheme instanceof Object)) return _validate({ $type: scheme }, arg, base);
if (scheme.$validate) {
const result = scheme.$validate(arg);
if (result === false) throw new ValidationError(base);
}
if (scheme.$group) _validate(scheme.$group, arg, base);
if (scheme.$or) {
if (scheme.$or instanceof Array) {
for (const s of scheme.$or) {
let success = false;
try {
_validate(s, arg, base);
success = true;
} catch { }
// TODO beautify output
if (!success) throw new ValidationError(scheme.$or);
}
} else {
for (const skey in scheme.$or) {
let success = false;
try {
_validate(scheme.$or[skey], arg[skey], `${base}.${skey}`);
success = true;
} catch { }
// TODO beautify output
if (!success) throw new ValidationError(base, scheme.$or);
}
}
}
if (scheme.$and) for (const s of scheme.$and) _validate(s, arg, base);
if (scheme.$type) {
if (typeof scheme.$type === 'string') {
if (!scheme.$type.endsWith('?')) {
if (!typeof arg === scheme.$type) {
throw new ValidationError(base);
}
} else if (arg && !scheme.$type.includes(typeof arg)) {
throw new ValidationError(base);
}
} else if (!(arg instanceof scheme.$type)) {
throw new ValidationError(base);
}
}
if (scheme.$eq && scheme.$eq !== arg) throw new ValidationError(base);
if (scheme.$not && scheme.$not === arg) throw new ValidationError(base);
if (scheme.$in && !(scheme.$in.includes(arg))) throw new ValidationError(base);
if (scheme.$nin && scheme.$nin.includes(arg)) throw new ValidationError(base);
for (const key in scheme) {
if (key[0] !== '$') {
if (arg instanceof Object) {
_validate(scheme[key], arg[key], `${base}.${key}`);
} else {
throw new ValidationError(base);
}
}
}
}
export function validate(scheme: any, arg: any) {
return _validate(scheme, arg, '');
}
export const isTitle = (s) => s && s.length < 64;
export const checkTitle = (s) => { if (!(s && s.length < 64)) throw new ValidationError('title'); else return s; };
export const isUid = (s) => RE_UID.test(s);
export const checkUid = (s) => { if (!isUid(s)) throw new ValidationError('uid'); else return s; };
export const isUname = (s) => RE_UNAME.test(s);
export const checkUname = (s) => { if (!isUname(s)) throw new ValidationError('uname'); else return s; };
export const isRole = (s) => RE_ROLE.test(s);
export const checkRole = (s) => { if (!isRole(s)) throw new ValidationError('role'); else return s; };
export const isPassword = (s) => s.length >= 5;
export const checkPassword = (s) => { if (!(s && s.length >= 5)) throw new ValidationError('password'); else return s; };
export const isEmail = (s) => RE_MAIL.test(s);
export const checkEmail = (s) => { if (!RE_MAIL.test(s)) throw new ValidationError('mail'); else return s; };
export const isContent = (s: any) => s && s.length < 65536;
export const checkContent = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('content'); else return s; };
export const isName = (s) => s && s.length < 256;
export const checkName = (s) => { if (!isName(s)) throw new ValidationError('name'); else return s; };
export const isPid = (s) => RE_PID.test(s.toString());
export const checkPid = (s) => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
export const isIntro = () => true;
export const checkIntro = (s) => { if (!isIntro()) throw new ValidationError('intro'); else return s; };
export const isDescription = (s: any) => s && s.length < 65536;
export const checkDescription = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('description'); else return s; };
global.Hydro.lib.validator = {
validate,
isTitle,
checkTitle,
isUid,
checkUid,
isUname,
checkUname,
isRole,
checkRole,
isPassword,
checkPassword,
isEmail,
checkEmail,
isContent,
checkContent,
isName,
checkName,
isPid,
checkPid,
isIntro,
checkIntro,
isDescription,
checkDescription,
};
/*
ID_RE = re.compile(r'[^\\/\s\u3000]([^\\/\n\r]*[^\\/\s\u3000])?')
def is_id(s):
return bool(ID_RE.fullmatch(s))
def check_category_name(s):
if not is_id(s):
raise error.ValidationError('category_name')
def check_node_name(s):
if not is_id(s):
raise error.ValidationError('node_name')
def is_intro(s):
return isinstance(s, str) and 0 < len(s.strip()) < 500
def check_intro(s):
if not is_intro(s):
raise error.ValidationError('intro')
def is_description(s):
return isinstance(s, str) and len(s) < 65536
def check_description(s):
if not is_description(s):
raise error.ValidationError('description')
def is_lang(i):
return i in export const ant.language.LANG_TEXTS
def check_lang(i):
if not is_lang(i):
raise error.ValidationError('lang')
*/