Skip to content

Commit baf5b15

Browse files
committed
support windows printer & balloon receipt
1 parent 57215a7 commit baf5b15

File tree

4 files changed

+117
-51
lines changed

4 files changed

+117
-51
lines changed

packages/server/client/balloon.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,30 @@ const logger = new Logger('fetcher');
4949
let timer = null;
5050
let printer = null;
5151

52+
async function getReceiptStatus(receipt) {
53+
const lp = receipt.split('/').pop();
54+
const oldPrinter = printer;
55+
printer = {
56+
printer: lp,
57+
info: fs.readFileSync(`/sys/class/usb/${lp}/device/ieee1284_id`, 'utf8').trim(),
58+
};
59+
if (!oldPrinter || oldPrinter.info === printer.info) return;
60+
logger.info('Printer changed:', printer.printer, printer.info);
61+
const usbDevices = fs.readdirSync('/dev/usb');
62+
for (const f of usbDevices) {
63+
if (f.startsWith('lp')) {
64+
const lpid = fs.readFileSync(`/sys/class/usb/${f}/device/ieee1284_id`, 'utf8').trim();
65+
if (lpid === oldPrinter.info) {
66+
logger.info('Printer found:', f, ':', lpid);
67+
oldPrinter.printer = f;
68+
printer = oldPrinter;
69+
break;
70+
}
71+
}
72+
}
73+
if (oldPrinter.info !== printer.info) throw Error('Printer not found, please check the printer connection.');
74+
}
75+
5276
async function printBalloon(doc) {
5377
const bReceipt = receiptText(
5478
doc.balloonid,
@@ -60,6 +84,7 @@ async function printBalloon(doc) {
6084
doc.total ? Object.keys(doc.total).map((k) => `- ${k}: ${doc.total[k].color}`).join('\n') : 'N/A',
6185
);
6286
if (printer) {
87+
await getReceiptStatus(printer.printer);
6388
fs.writeFileSync(path.resolve(printer), bReceipt);
6489
}
6590
}
@@ -86,6 +111,6 @@ async function fetchTask(c) {
86111
}
87112

88113
export async function apply() {
89-
printer = config.balloon;
114+
await getReceiptStatus(config.balloon);
90115
if (config.token && config.server && config.balloon) await fetchTask(config);
91116
}

packages/server/client/printer.ts

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
/* eslint-disable no-await-in-loop */
22
import path from 'path';
3-
// https://www.sumatrapdfreader.org//dl//rel/3.1.2/SumatraPDF-3.1.2.zip
4-
import winPrint from '@myteril/node-win-printer';
53
import superagent from 'superagent';
6-
import { getPrinters, print } from 'unix-print';
74
import { config, saveConfig } from '../config';
8-
import { fs, Logger, sleep } from '../utils';
5+
import {
6+
fs, getPrinters, initWinPrinter, Logger, print, sleep,
7+
} from '../utils';
98
import { createTypstCompiler, generateTypst } from './typst';
109

1110
let compiler;
@@ -14,41 +13,6 @@ const logger = new Logger('printer');
1413

1514
let timer = null;
1615

17-
async function fetchTask(c) {
18-
if (timer) clearTimeout(timer);
19-
logger.info('Fetching Task from tools server...');
20-
try {
21-
const printersInfo = await getPrinters();
22-
const { body } = await superagent.post(`${c.server}/client/${c.token}/print`)
23-
.send({
24-
printers: config.printers,
25-
printersInfo: JSON.stringify(printersInfo.map((p) => ({
26-
printer: p.printer,
27-
status: p.status,
28-
description: p.description,
29-
}))),
30-
});
31-
if (body.setPrinter) {
32-
config.printers = body.setPrinter;
33-
saveConfig();
34-
logger.info(`Printer set to ${config.printers}`);
35-
}
36-
if (body.doc) {
37-
logger.info(`Print task ${body.doc.tid}#${body.doc._id}...`);
38-
await printFile(body.doc);
39-
await superagent.post(`${c.server}/client/${c.token}/doneprint/${body.doc._id}`);
40-
logger.info(`Print task ${body.doc.tid}#${body.doc._id} completed.`);
41-
} else {
42-
logger.info('No print task, sleeping...');
43-
await sleep(5000);
44-
}
45-
} catch (e) {
46-
logger.error(e);
47-
await sleep(5000);
48-
}
49-
timer = setTimeout(() => fetchTask(c), 3000);
50-
}
51-
5216
export async function ConvertCodeToPDF(code, lang, filename, team, location) {
5317
compiler ||= await createTypstCompiler();
5418
const typst = generateTypst(team, location, filename, lang);
@@ -77,14 +41,14 @@ export async function printFile(doc) {
7741
const randomP = printers[Math.floor(Math.random() * printers.length)];
7842
if (randomP.status === 'idle') {
7943
logger.info(`Printing ${_id} on ${randomP.printer}`);
80-
await print(path.resolve(process.cwd(), `data/${tid}#${_id}.pdf`), randomP.printer, ['-P', '1-5']);
44+
await print(path.resolve(process.cwd(), `data/${tid}#${_id}.pdf`), randomP.printer, 1, 5);
8145
return;
8246
}
8347
for (const printer of printers.filter((p) => p.printer !== randomP.printer)) {
8448
logger.info(`Checking ${printer.printer} ${printer.status}`);
8549
if (printer.status === 'idle') {
8650
logger.info(`Printing ${_id} on ${printer.printer}`);
87-
await print(path.resolve(process.cwd(), `data/${tid}#${_id}.pdf`), printer.printer, ['-P', '1-5']);
51+
await print(path.resolve(process.cwd(), `data/${tid}#${_id}.pdf`), printer.printer, 1, 5);
8852
return;
8953
}
9054
}
@@ -97,7 +61,50 @@ export async function printFile(doc) {
9761
}
9862
}
9963

64+
async function fetchTask(c) {
65+
if (timer) clearTimeout(timer);
66+
logger.info('Fetching Task from tools server...');
67+
try {
68+
const printersInfo = await getPrinters();
69+
const { body } = await superagent.post(`${c.server}/client/${c.token}/print`)
70+
.send({
71+
printers: config.printers,
72+
printersInfo: JSON.stringify(printersInfo.map((p) => ({
73+
printer: p.printer,
74+
status: p.status,
75+
description: p.description,
76+
}))),
77+
});
78+
if (body.setPrinter) {
79+
config.printers = body.setPrinter;
80+
saveConfig();
81+
logger.info(`Printer set to ${config.printers}`);
82+
}
83+
if (body.doc) {
84+
logger.info(`Print task ${body.doc.tid}#${body.doc._id}...`);
85+
await printFile(body.doc);
86+
await superagent.post(`${c.server}/client/${c.token}/doneprint/${body.doc._id}`);
87+
logger.info(`Print task ${body.doc.tid}#${body.doc._id} completed.`);
88+
} else {
89+
logger.info('No print task, sleeping...');
90+
await sleep(5000);
91+
}
92+
} catch (e) {
93+
logger.error(e);
94+
await sleep(5000);
95+
}
96+
timer = setTimeout(() => fetchTask(c), 3000);
97+
}
98+
10099
export async function apply() {
101100
compiler = await createTypstCompiler();
101+
if (process.platform === 'win32') {
102+
try {
103+
initWinPrinter();
104+
} catch (e) {
105+
logger.error(e);
106+
process.exit(1);
107+
}
108+
}
102109
if (config.token && config.server && config.printers?.length) await fetchTask(config);
103110
}

packages/server/config.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,25 @@ token:
2828
username:
2929
password:
3030
`;
31-
31+
let printers = [];
32+
if (isClient) {
33+
printers = await getPrinters().then((r) => r.map((p) => p.printer)).catch(() => []);
34+
logger.info(printers.length, 'printers found:', printers.map((p) => p.printer).join(', '));
35+
if (process.platform === 'linux') {
36+
const usbDevices = fs.readdirSync('/dev/usb');
37+
for (const f of usbDevices) {
38+
if (f.startsWith('lp')) {
39+
const lpid = fs.readFileSync(`/sys/class/usbmisc/${f}/device/ieee1284_id`, 'utf8').trim();
40+
logger.info('USB Printer found:', f, ':', lpid);
41+
logger.info('If you want to use this printer for balloon print, set balloon:', f, 'in config.yaml.');
42+
}
43+
}
44+
} else logger.info('If you want to use balloon client, please run this on linux.');
45+
}
3246
const clientConfigDefault = yaml.dump({
3347
server: '',
3448
balloon: '',
35-
printers: await getPrinters().then((r) => r.map((p) => p.printer)).catch(() => []),
49+
printers,
3650
token: '',
3751
});
3852
fs.writeFileSync(configPath, isClient ? clientConfigDefault : serverConfigDefault);

packages/server/utils/printers.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import fs from 'fs';
2+
import path from 'path';
23
import winPrint from '@myteril/node-win-printer';
34
import unixPrint from 'unix-print';
4-
import path from 'path';
55

66
let winPrinter: winPrint.PDFPrinter;
77

8-
if (process.platform === 'win32') {
8+
export interface Printer {
9+
printer: string;
10+
description?: string;
11+
status?: string;
12+
alerts?: string;
13+
connection?: string;
14+
}
15+
16+
export function initWinPrinter() {
917
const execPath = [
1018
'./SumatraPDF.exe',
1119
path.resolve(__dirname, 'SumatraPDF.exe'),
@@ -15,26 +23,38 @@ if (process.platform === 'win32') {
1523
];
1624
const sumatraPdfPath = execPath.find((p) => fs.existsSync(p));
1725
if (!sumatraPdfPath) {
18-
throw new Error('SumatraPDF not found, please install it on https://www.sumatrapdfreader.org/download-free-pdf-viewer');
26+
throw new Error(`SumatraPDF not found, please install it on https://www.sumatrapdfreader.org/download-free-pdf-viewer,
27+
or direct download from https://www.sumatrapdfreader.org/dl/rel/3.1.2/SumatraPDF-3.1.2.zip`);
1928
}
2029
winPrinter = new winPrint.PDFPrinter({
2130
sumatraPdfPath,
2231
});
2332
}
2433

25-
export async function getPrinters() {
34+
const windowsPrinterStatus = {
35+
3: 'idle',
36+
4: 'printing',
37+
};
38+
39+
export async function getPrinters(): Promise<Printer[]> {
2640
if (process.platform === 'win32') {
27-
return winPrint.getPrinters();
41+
const winprinters = await winPrint.getPrinters();
42+
return winprinters.map((p: any) => ({
43+
printer: p.DriverName,
44+
description: p.Caption,
45+
status: windowsPrinterStatus[p.PrinterStatus] ? windowsPrinterStatus[p.PrinterStatus] : 'unknown',
46+
}));
2847
}
2948
return unixPrint.getPrinters();
3049
}
3150

32-
export async function print(printer, file) {
51+
export async function print(printer: string, file: string, startPage?: number, endPage?: number) {
3352
if (process.platform === 'win32') {
3453
return winPrinter.print({
3554
file,
3655
printer,
56+
pages: startPage && endPage ? [{ start: startPage, end: endPage }] : undefined,
3757
});
3858
}
39-
return unixPrint.print(printer, file);
59+
return unixPrint.print(printer, file, startPage && endPage ? ['-P', `${startPage}-${endPage}`] : []);
4060
}

0 commit comments

Comments
 (0)