Skip to content

Commit 1027862

Browse files
committed
save
1 parent ba96828 commit 1027862

19 files changed

+269
-72
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ config.yaml
1414
config.json
1515
config.*.yaml
1616
config.*.json
17-
data/
17+
data*
1818
yarn.lock
1919
pnpm-lock.yaml
2020

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
diff --git a/lib/index.js b/lib/index.js
2+
index 2cfbc38128a6387e03e30df13422584cf51636fb..97d08d3459b6e06128955fbdd1ac56ab676c5427 100644
3+
--- a/lib/index.js
4+
+++ b/lib/index.js
5+
@@ -68,53 +68,14 @@ var getPrinters = function () { return __awaiter(void 0, void 0, void 0, functio
6+
case 0: return [4 /*yield*/, getTerminalCodepage()];
7+
case 1:
8+
codepage = _b.sent();
9+
- return [4 /*yield*/, execAsync('wmic printer list /format:list', { encoding: 'binary' })];
10+
+ return [4 /*yield*/, execAsync('powershell "Get-WmiObject -Class Win32_Printer | ConvertTo-Json"', { encoding: 'binary' })];
11+
case 2:
12+
_a = _b.sent(), stdout = _a.stdout, stderr = _a.stderr;
13+
if (stderr.length > 0) {
14+
return [2 /*return*/, null];
15+
}
16+
result = iconv_lite_1.default.decode(Buffer.from(stdout, 'binary'), "cp".concat(codepage));
17+
- blocks = result.replace(/\r/ig, '').split('\n\n\n');
18+
- printerInfos = blocks.map(function (block) {
19+
- var obj = {};
20+
- // Split the block into lines that contain an attribute and its value.
21+
- var lines = block.trim().split('\n');
22+
- lines.forEach(function (line) {
23+
- // If the line is empty, ignore it.
24+
- if (line.trim().length < 1)
25+
- return;
26+
- // Get the index of the first equals sign.
27+
- var equalsSignIndex = line.indexOf('=');
28+
- // Extract the name and the value of the attribute.
29+
- var attributeName = line.substring(0, equalsSignIndex);
30+
- var attributeValue = line.substring(equalsSignIndex + 1);
31+
- // Ignore attributes that have blank values for compact output.
32+
- if (attributeValue.length < 1)
33+
- return;
34+
- // Array
35+
- if (attributeValue.startsWith('{') && attributeValue.endsWith('}'))
36+
- attributeValue = JSON.parse("[".concat(attributeValue.substring(1, attributeValue.length - 1), "]"));
37+
- // Boolean
38+
- if (typeof attributeValue === 'string') {
39+
- switch (attributeValue) {
40+
- case 'TRUE':
41+
- attributeValue = true;
42+
- break;
43+
- case 'FALSE':
44+
- attributeValue = false;
45+
- break;
46+
- }
47+
- }
48+
- // Numbers
49+
- if (typeof attributeValue === 'string' && !isNaN(parseFloat(attributeValue))) {
50+
- attributeValue = Number(attributeValue).valueOf();
51+
- }
52+
- // Assign the attribute value by its name into the object.
53+
- obj[attributeName] = attributeValue;
54+
- });
55+
- return obj;
56+
- });
57+
+ printerInfos = JSON.parse(result)
58+
// Ignore the empty printer info objects.
59+
printerInfos = printerInfos.filter(function (printerInfo) { return Object.keys(printerInfo).length > 0; });
60+
return [2 /*return*/, printerInfos];
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
diff --git a/lib/http-proxy/passes/web-incoming.js b/lib/http-proxy/passes/web-incoming.js
2+
index 7ae735514190eea569c605fff7d27c045fe8d601..88953ebc549d4e56dc26e46aa32211b82e6546a1 100644
3+
--- a/lib/http-proxy/passes/web-incoming.js
4+
+++ b/lib/http-proxy/passes/web-incoming.js
5+
@@ -143,8 +143,11 @@ module.exports = {
6+
}
7+
8+
// Ensure we abort proxy if request is aborted
9+
- req.on('aborted', function () {
10+
- proxyReq.abort();
11+
+ res.on('close', function () {
12+
+ var aborted = !res.writableFinished;
13+
+ if (aborted) {
14+
+ proxyReq.abort();
15+
+ }
16+
});
17+
18+
// handle errors in proxy and incoming request, just like for forward proxy
19+
@@ -154,7 +157,7 @@ module.exports = {
20+
21+
function createErrorHandler(proxyReq, url) {
22+
return function proxyError(err) {
23+
- if (req.socket.destroyed && err.code === 'ECONNRESET') {
24+
+ if ((req.aborted || req.destroyed || req.socket?.destroyed) && err.code === 'ECONNRESET') {
25+
server.emit('econnreset', err, req, res, url);
26+
return proxyReq.abort();
27+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"typescript": "5.4.5"
3030
},
3131
"resolutions": {
32-
"formidable": "patch:formidable@npm%3A2.1.2#~/.yarn/patches/formidable-npm-2.1.2-40ba18d67f.patch"
32+
"formidable": "patch:formidable@npm%3A2.1.2#~/.yarn/patches/formidable-npm-2.1.2-40ba18d67f.patch",
33+
"http-proxy@npm:^1.18.1": "patch:http-proxy@npm%3A1.18.1#~/.yarn/patches/http-proxy-npm-1.18.1-a313c479c5.patch"
3334
}
3435
}

packages/machine-setup/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12-
"@neutralinojs/lib": "^5.4.0",
12+
"@neutralinojs/lib": "^5.5.0",
1313
"vue": "^3.5.12"
1414
},
1515
"devDependencies": {

packages/machine-setup/neutralino.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"binaryName": "hydro-xcpctools-machine-setup",
4444
"resourcesPath": "/frontend/",
4545
"extensionsPath": "/extensions/",
46-
"binaryVersion": "5.4.0",
46+
"binaryVersion": "5.5.0",
4747
"clientVersion": "5.4.0",
4848
"frontendLibrary": {
4949
"projectPath": "/frontend/",

packages/server/client/balloon.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import EscPosEncoder from '@freedom_sky/esc-pos-encoder';
33
import superagent from 'superagent';
44
import { config } from '../config';
55
import {
6-
checkReceiptStatus, convertToChinese, Logger, receiptPrint, sleep,
6+
checkReceiptStatus, getBalloonName, Logger, receiptPrint, sleep,
77
} from '../utils';
88

99
const post = (url: string) => superagent.post(new URL(url, config.server).toString()).set('Accept', 'application/json');
@@ -30,7 +30,7 @@ const i18n = {
3030
},
3131
};
3232

33-
export const receiptText = (
33+
export const receiptBalloonText = (
3434
id: number, location: string, problem: string, color: string, comment: string, teamname: string, status: string, lang: 'zh' | 'en' = 'zh',
3535
) => encoder
3636
.initialize()
@@ -66,6 +66,18 @@ export const receiptText = (
6666
.cut()
6767
.encode();
6868

69+
export const plainBalloonText = (
70+
id: number, location: string, problem: string, color: string, comment: string, teamname: string, status: string, lang: 'zh' | 'en' = 'zh',
71+
) => `
72+
${i18n[lang].receipt}
73+
ID: ${id}
74+
${i18n[lang].location}: ${location}
75+
${i18n[lang].team}: ${teamname}
76+
${i18n[lang].problem}: ${problem}
77+
${i18n[lang].color}: ${color}
78+
${i18n[lang].comment}: ${comment}
79+
`;
80+
6981
const logger = new Logger('balloon');
7082

7183
let timer = null;
@@ -74,20 +86,21 @@ let printer = null;
7486
async function printBalloon(doc, lang) {
7587
let status = '';
7688
for (const i in doc.total) {
77-
status += `- ${i}: ${lang === 'zh' ? await convertToChinese(doc.total[i].color) : doc.total[i].color}\n`;
89+
status += `- ${i}: ${getBalloonName(doc.total[i].color, lang)}\n`;
7890
}
79-
const bReceipt = receiptText(
91+
const genText = config.balloonType === 'plain' ? plainBalloonText : receiptBalloonText;
92+
const bReceipt = await genText(
8093
doc.balloonid,
8194
doc.location ? doc.location : 'N/A',
8295
doc.problem,
83-
lang === 'zh' ? await convertToChinese(doc.contestproblem.color) : doc.contestproblem.color,
96+
getBalloonName(doc.contestproblem.rgb, lang),
8497
doc.awards ? doc.awards : 'N/A',
8598
doc.team,
8699
status,
87100
lang,
88101
);
89102
printer = await checkReceiptStatus(printer);
90-
await receiptPrint(printer, bReceipt);
103+
await receiptPrint(printer, bReceipt, config.balloonCommand);
91104
}
92105

93106
async function fetchTask(c) {

packages/server/client/printer.ts

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable no-await-in-loop */
22
import path from 'path';
3+
import { PDFDocument } from 'pdf-lib';
34
import superagent from 'superagent';
45
import { config, saveConfig } from '../config';
56
import {
@@ -14,42 +15,75 @@ const logger = new Logger('printer');
1415

1516
let timer = null;
1617

18+
const mergePDFs = async (files: string[], output: string) => {
19+
const pdf = await PDFDocument.create();
20+
pdf.setProducer('pdf-merger-js');
21+
pdf.setCreationDate(new Date());
22+
for (const file of files) {
23+
const srcDoc = await PDFDocument.load(fs.readFileSync(file));
24+
const srcPageCount = srcDoc.getPageCount();
25+
logger.info(`${file} has ${srcPageCount} pages`);
26+
const copiedPages = await pdf.copyPages(
27+
srcDoc,
28+
Array.from({ length: config.printPageMax > srcPageCount ? srcPageCount : config.printPageMax }, (_, i) => i),
29+
);
30+
for (const page of copiedPages) {
31+
pdf.addPage(page);
32+
}
33+
}
34+
logger.info(`Merged ${files.length} files into ${output}`);
35+
return fs.writeFileSync(output, await pdf.save());
36+
};
37+
1738
export async function ConvertCodeToPDF(code, lang, filename, team, location, codeColor = false) {
1839
compiler ||= await createTypstCompiler();
19-
const typst = generateTypst(team, location, filename, lang, codeColor);
40+
const fakeFilename = String.random(8); // cubercsl: do not trust filename from user
41+
const typst = generateTypst(team, location, fakeFilename, filename, lang, codeColor);
2042
compiler.addSource('/main.typst', typst);
21-
compiler.addSource(`/${filename}`, code);
43+
compiler.addSource(`/${fakeFilename}`, code);
2244
const docs = await compiler.compile({
2345
format: 'pdf',
2446
mainFilePath: '/main.typst',
2547
});
48+
compiler.addSource(`/${fakeFilename}`, '');
2649
logger.info(`Convert ${filename} to PDF`);
2750
return docs;
2851
}
2952

30-
export async function printFile(doc) {
31-
const {
32-
_id, tid, code, lang, filename, team, location,
33-
} = doc;
53+
export async function printFile(docs) {
3454
try {
35-
const docs = await ConvertCodeToPDF(code || 'empty file', lang, filename, team, location, config.printColor);
36-
fs.writeFileSync(path.resolve(process.cwd(), `data${path.sep}${tid}#${_id}.pdf`), docs);
55+
let finalFile = null;
56+
const files = [];
57+
for (const doc of docs) {
58+
const {
59+
_id, tid, code, lang, filename, team, location,
60+
} = doc;
61+
const pdf = await ConvertCodeToPDF(code || 'empty file', lang, filename, team, location, config.printColor);
62+
fs.writeFileSync(path.resolve(process.cwd(), `data${path.sep}${tid}#${_id}.pdf`), pdf);
63+
files.push(path.resolve(process.cwd(), `data${path.sep}${tid}#${_id}.pdf`));
64+
}
65+
if (files.length === 1) {
66+
finalFile = files[0];
67+
} else {
68+
finalFile = path.resolve(process.cwd(), `data${path.sep}${new Date().getTime()}-merged.pdf`);
69+
await mergePDFs(files, finalFile);
70+
}
3771
if (config.printers.length) {
3872
// eslint-disable-next-line no-constant-condition
3973
while (true) {
4074
const printersInfo: any[] = await getPrinters();
4175
const printers = printersInfo.filter((p) => config.printers.includes(p.printer));
4276
const randomP = printers[Math.floor(Math.random() * printers.length)];
4377
if (randomP.status === 'idle') {
44-
logger.info(`Printing ${_id} on ${randomP.printer}`);
45-
await print(path.resolve(process.cwd(), `data/${tid}#${_id}.pdf`), randomP.printer, 1, 5);
78+
logger.info(`Printing ${finalFile} on ${randomP.printer}`);
79+
await print(finalFile, randomP.printer, 1, files.length > 1 ? undefined : 5);
4680
return randomP.printer;
4781
}
4882
for (const printer of printers.filter((p) => p.printer !== randomP.printer)) {
4983
logger.info(`Checking ${printer.printer} ${printer.status}`);
5084
if (printer.status === 'idle') {
51-
logger.info(`Printing ${_id} on ${printer.printer}`);
52-
await print(path.resolve(process.cwd(), `data/${tid}#${_id}.pdf`), printer.printer, 1, 5);
85+
logger.info(`Printing ${finalFile} on ${printer.printer}`);
86+
await print(finalFile, printer.printer, 1, files.length > 1 ? undefined : 5);
5387
return printer.printer;
5488
}
5589
}
@@ -70,26 +104,42 @@ async function fetchTask(c) {
70104
logger.info('Fetching Task from tools server...');
71105
try {
72106
const printersInfo: any[] = await getPrinters();
73-
const { body } = await post(`${c.server}/client/${c.token}/print`)
74-
.send({
75-
printers: config.printers,
76-
printersInfo: JSON.stringify(printersInfo.map((p) => ({
77-
printer: p.printer,
78-
status: p.status,
79-
description: p.description,
80-
}))),
81-
});
82-
if (body.setPrinter) {
83-
config.printers = body.setPrinter;
84-
saveConfig();
85-
logger.info(`Printer set to ${config.printers}`);
107+
const tasks = [];
108+
for (let i = 0; i < config.printMergeQueue; i++) {
109+
const { body } = await post(`${c.server}/client/${c.token}/print`)
110+
.send({
111+
printers: config.printers,
112+
printersInfo: JSON.stringify(printersInfo.map((p) => ({
113+
printer: p.printer,
114+
status: p.status,
115+
description: p.description,
116+
}))),
117+
});
118+
if (body.setPrinter) {
119+
config.printers = body.setPrinter;
120+
saveConfig();
121+
logger.info(`Printer set to ${config.printers}`);
122+
}
123+
if (body.doc) {
124+
tasks.push(body.doc);
125+
// FIXME: so ugly, give server merge task number
126+
if (config.printMergeQueue !== 1) await post(`${c.server}/client/${c.token}/doneprint/${body.doc._id}`);
127+
}
86128
}
87-
if (body.doc) {
88-
logger.info(`Print task ${body.doc.tid}#${body.doc._id}...`);
89-
const printer = await printFile(body.doc);
90-
if (!printer) throw new Error('No Printer Configured');
91-
await post(`${c.server}/client/${c.token}/doneprint/${body.doc._id}?printer=${JSON.stringify(printer)}`);
92-
logger.info(`Print task ${body.doc.tid}#${body.doc._id} completed.`);
129+
if (tasks.length) {
130+
logger.info(`Print task ${tasks.map((t) => `${t.tid}#${t._id}`).join(', ')}...`);
131+
let printer = null;
132+
try {
133+
printer = await printFile(tasks);
134+
if (!printer) throw new Error('No Printer Configured');
135+
} catch (e) {
136+
logger.error(e);
137+
throw e;
138+
}
139+
for (const doc of tasks) {
140+
await post(`${c.server}/client/${c.token}/doneprint/${doc._id}?printer=${JSON.stringify(printer)}`);
141+
logger.info(`Print task ${doc.tid}#${doc._id} completed.`);
142+
}
93143
} else {
94144
logger.info('No print task, sleeping...');
95145
await sleep(5000);

packages/server/client/typst.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class TypstCompilerDriver {
5959
}
6060
}
6161

62-
export function generateTypst(team: string, location: string, filename: string, lang: string, codeColor: boolean) {
62+
export function generateTypst(team: string, location: string, filename: string, originalFilename: string, lang: string, codeColor: boolean) {
6363
return `
6464
#let fit(name: "", width: 147mm) = {
6565
context {
@@ -82,6 +82,7 @@ export function generateTypst(team: string, location: string, filename: string,
8282
team: "",
8383
location: "",
8484
filename: "",
85+
original: "",
8586
lang: "",
8687
) = {
8788
set document(author: (team), title: filename)
@@ -94,7 +95,7 @@ export function generateTypst(team: string, location: string, filename: string,
9495
}
9596
#fit(name: team)
9697
#linebreak()
97-
filename: #filename
98+
filename: #original
9899
#h(1fr)
99100
By Hydro/XCPC-TOOLS | Page #counter(page).display("1 of 1", both: true)
100101
],
@@ -112,13 +113,14 @@ export function generateTypst(team: string, location: string, filename: string,
112113
}
113114
code
114115
}
115-
raw(read(filename), lang: lang, block: true${codeColor ? '' : ', theme: "BW.tmtheme"'})
116+
raw(read(filename), lang: lang, block: true${codeColor ? '' : ', theme: "/XCPCTOOLS/BW.tmtheme"'})
116117
}
117118
118119
#print(
119120
team: ${JSON.stringify(team || '')},
120121
location: ${JSON.stringify(location || '')},
121122
filename: ${JSON.stringify(filename || '')},
123+
original: ${JSON.stringify(originalFilename || filename || '')},
122124
lang: ${JSON.stringify(lang || '')}
123125
)`;
124126
}
@@ -129,6 +131,6 @@ export const BWTmTheme = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist
129131
export async function createTypstCompiler() {
130132
const compiler = new TypstCompilerDriver();
131133
await compiler.init();
132-
compiler.addSource('/BW.tmtheme', BWTmTheme);
134+
compiler.addSource('/XCPCTOOLS/BW.tmtheme', BWTmTheme);
133135
return compiler;
134136
}

0 commit comments

Comments
 (0)