1
1
/* eslint-disable no-await-in-loop */
2
2
import path from 'path' ;
3
+ import { PDFDocument } from 'pdf-lib' ;
3
4
import superagent from 'superagent' ;
4
5
import { config , saveConfig } from '../config' ;
5
6
import {
@@ -14,42 +15,75 @@ const logger = new Logger('printer');
14
15
15
16
let timer = null ;
16
17
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
+
17
38
export async function ConvertCodeToPDF ( code , lang , filename , team , location , codeColor = false ) {
18
39
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 ) ;
20
42
compiler . addSource ( '/main.typst' , typst ) ;
21
- compiler . addSource ( `/${ filename } ` , code ) ;
43
+ compiler . addSource ( `/${ fakeFilename } ` , code ) ;
22
44
const docs = await compiler . compile ( {
23
45
format : 'pdf' ,
24
46
mainFilePath : '/main.typst' ,
25
47
} ) ;
48
+ compiler . addSource ( `/${ fakeFilename } ` , '' ) ;
26
49
logger . info ( `Convert ${ filename } to PDF` ) ;
27
50
return docs ;
28
51
}
29
52
30
- export async function printFile ( doc ) {
31
- const {
32
- _id, tid, code, lang, filename, team, location,
33
- } = doc ;
53
+ export async function printFile ( docs ) {
34
54
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
+ }
37
71
if ( config . printers . length ) {
38
72
// eslint-disable-next-line no-constant-condition
39
73
while ( true ) {
40
74
const printersInfo : any [ ] = await getPrinters ( ) ;
41
75
const printers = printersInfo . filter ( ( p ) => config . printers . includes ( p . printer ) ) ;
42
76
const randomP = printers [ Math . floor ( Math . random ( ) * printers . length ) ] ;
43
77
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 ) ;
46
80
return randomP . printer ;
47
81
}
48
82
for ( const printer of printers . filter ( ( p ) => p . printer !== randomP . printer ) ) {
49
83
logger . info ( `Checking ${ printer . printer } ${ printer . status } ` ) ;
50
84
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 ) ;
53
87
return printer . printer ;
54
88
}
55
89
}
@@ -70,26 +104,42 @@ async function fetchTask(c) {
70
104
logger . info ( 'Fetching Task from tools server...' ) ;
71
105
try {
72
106
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
+ }
86
128
}
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
+ }
93
143
} else {
94
144
logger . info ( 'No print task, sleeping...' ) ;
95
145
await sleep ( 5000 ) ;
0 commit comments