@@ -26,6 +26,13 @@ const axios = require('axios').default;
26
26
const Logger = require ( './log' ) ;
27
27
const Judge = require ( './index' ) ;
28
28
29
+ const languageFileExension = {
30
+ 'cpp98' : 'cpp' , 'cpp03' : 'cpp' , 'cpp11' : 'cpp' , 'cpp14' : 'cpp' , 'cpp17' : 'cpp' , 'cpp20' : 'cpp' ,
31
+ 'c99' : 'c' , 'c11' : 'c' , 'c17' : 'c' ,
32
+ 'nodejs14' : 'js' ,
33
+ 'scratch3' : 'sb3' , 'clipcc3' : 'ccproj'
34
+ } ;
35
+
29
36
class JudgeClient {
30
37
constructor ( ) {
31
38
this . ws = null ;
@@ -35,6 +42,11 @@ class JudgeClient {
35
42
36
43
const configPath = 'h2oj-judge.yml' ;
37
44
this . config = yaml . parse ( fs . readFileSync ( configPath , { encoding : 'utf-8' } ) ) ;
45
+
46
+ this . createDirectoryWithCheck ( path . join ( this . config . cache_dir ) , true ) ;
47
+ this . createDirectoryWithCheck ( path . join ( this . config . cache_dir , 'problem' ) ) ;
48
+ this . createDirectoryWithCheck ( path . join ( this . config . cache_dir , 'judge' ) ) ;
49
+ this . createDirectoryWithCheck ( path . join ( this . config . cache_dir , 'checker' ) ) ;
38
50
39
51
this . axios = axios . create ( {
40
52
baseURL : this . config . server_url ,
@@ -46,6 +58,11 @@ class JudgeClient {
46
58
} ) ;
47
59
}
48
60
61
+ sendJSON ( data ) {
62
+ this . logger . log ( 'Data sended: ' , JSON . stringify ( data ) ) ;
63
+ return this . ws . send ( JSON . stringify ( data ) ) ;
64
+ }
65
+
49
66
resetAxios ( ) {
50
67
this . axios = axios . create ( {
51
68
baseURL : this . config . server_url ,
@@ -58,6 +75,16 @@ class JudgeClient {
58
75
} ) ;
59
76
}
60
77
78
+ getTime ( ) {
79
+ return Math . floor ( Number ( new Date ( ) ) / 1000 ) ;
80
+ }
81
+
82
+ createDirectoryWithCheck ( path , recursive = false ) {
83
+ if ( ! fs . existsSync ( path ) ) {
84
+ fs . mkdirSync ( path , { recursive : recursive } ) ;
85
+ }
86
+ }
87
+
61
88
async connectServer ( ) {
62
89
const res = await this . axios . post ( 'judge/verify' , {
63
90
token : this . config . client_token
@@ -72,16 +99,32 @@ class JudgeClient {
72
99
console . log ( wsURL ) ;
73
100
this . ws = new WebSocket ( wsURL ) ;
74
101
this . ws . on ( 'open' , ( ) => {
75
- this . logger . log ( 'on open ' ) ;
76
- this . ws . send ( JSON . stringify ( {
102
+ this . logger . log ( 'Websocket opened. ' ) ;
103
+ this . sendJSON ( {
77
104
token : this . config . client_token
78
- } ) ) ;
105
+ } ) ;
106
+ setInterval ( ( ) => {
107
+ this . sendJSON ( {
108
+ event : 'ping' ,
109
+ time : this . getTime ( )
110
+ } ) ;
111
+ } , 15000 ) ;
79
112
} ) ;
80
113
this . ws . on ( 'message' , msg => {
81
114
this . logger . log ( 'Data Received: ' , msg . toString ( ) ) ;
82
115
const data = JSON . parse ( msg ) ;
83
116
if ( data . event === 'judge' ) {
84
- this . judge ( data . data ) ;
117
+ this . judge ( data . data , status => {
118
+ this . sendJSON ( {
119
+ event : 'status' ,
120
+ data : status
121
+ } ) ;
122
+ } , result => {
123
+ this . sendJSON ( {
124
+ event : 'end' ,
125
+ data : result
126
+ } ) ;
127
+ } ) ;
85
128
}
86
129
} ) ;
87
130
this . ws . on ( 'close' , code => {
@@ -90,29 +133,43 @@ class JudgeClient {
90
133
this . ws . on ( 'error' , error => {
91
134
this . logger . err ( 'Websocket error: ' , error ) ;
92
135
} ) ;
93
- /*await new Promise((resolve) => {
94
- this.ws.once('open', async () => {
95
- if (!this.config.noStatus) {
96
- const info = await sysinfo.get();
97
- this.ws.send(JSON.stringify({ key: 'status', info }));
98
- setInterval(async () => {
99
- const [mid, inf] = await sysinfo.update();
100
- this.ws.send(JSON.stringify({ key: 'status', info: { mid, ...inf } }));
101
- }, 1200000);
102
- }
103
- resolve(null);
104
- });
105
- });*/
106
136
this . logger . log ( 'Connected.' ) ;
107
137
}
108
138
109
- async judge ( data ) {
110
- await this . checkDataCache ( data . problem_id . toString ( ) ) ;
139
+ setupWorkingDirectory ( dir_path , wk = true ) {
140
+ this . createDirectoryWithCheck ( dir_path ) ;
141
+ if ( wk ) this . createDirectoryWithCheck ( path . join ( dir_path , 'working' ) ) ;
142
+ this . createDirectoryWithCheck ( path . join ( dir_path , 'binary' ) ) ;
143
+ this . createDirectoryWithCheck ( path . join ( dir_path , 'source' ) ) ;
144
+ }
145
+
146
+ async judge ( data , callback , finish ) {
147
+ const submissionId = data . submission_id . toString ( ) ;
148
+ const problemId = data . problem_id . toString ( ) ;
149
+ this . setupWorkingDirectory ( path . join ( this . config . cache_dir , 'judge' , submissionId ) ) ;
150
+ await this . checkDataCache ( problemId ) ;
151
+ const fileName = await this . fetchSource ( submissionId ) ;
152
+ const checkerId = await this . checkChecker ( problemId ) ;
153
+
154
+ Judge . judge ( {
155
+ sandboxDirectory : this . config . sandbox_dir ,
156
+ workingDirectory : path . join ( this . config . cache_dir , 'judge' , submissionId ) ,
157
+ problemDirectory : path . join ( this . config . cache_dir , 'problem' , problemId ) ,
158
+ checkerPath : path . join ( this . config . cache_dir , 'checker' , checkerId , 'binary/checker' ) ,
159
+ sourceName : fileName ,
160
+ type : data . language
161
+ } , data => {
162
+ this . logger . log ( 'Judge testcase [' , data . id , ']:\n' , JSON . stringify ( data ) ) ;
163
+ callback ( data ) ;
164
+ } ) . then ( result => {
165
+ finish ( result ) ;
166
+ } ) ;
111
167
}
112
168
113
169
async fetchData ( problemId ) {
114
170
this . logger . log ( 'Fetching data: ' , problemId ) ;
115
- const filePath = path . join ( this . config . cache_dir , problemId ) ;
171
+ const filePath = path . join ( this . config . cache_dir , 'problem' , problemId ) ;
172
+ this . createDirectoryWithCheck ( filePath ) ;
116
173
const file = fs . createWriteStream ( path . join ( filePath , 'data.zip' ) ) ;
117
174
const res = await this . axios . get ( 'judge/get_data' , {
118
175
responseType : 'stream' ,
@@ -127,23 +184,103 @@ class JudgeClient {
127
184
} ) ;
128
185
}
129
186
187
+ async fetchSource ( submissionId ) {
188
+ this . logger . log ( 'Fetching src: ' , submissionId ) ;
189
+ const filePath = path . join ( this . config . cache_dir , 'judge' , submissionId , 'source' ) ;
190
+
191
+ const res = await this . axios . get ( 'judge/get_source' , {
192
+ responseType : 'stream' ,
193
+ params : {
194
+ submission_id : submissionId
195
+ }
196
+ } ) ;
197
+ let fileName = decodeURI ( RegExp ( "filename=\"([^;]+\\.[^\\.;]+)\";*" ) . exec ( res . headers [ 'content-disposition' ] ) [ 1 ] ) ;
198
+ console . log ( fileName ) ;
199
+ const file = fs . createWriteStream ( path . join ( filePath , fileName ) ) ;
200
+ res . data . pipe ( file ) ;
201
+ return fileName ;
202
+ }
203
+
204
+ readIntFromFile ( fileName ) {
205
+ try {
206
+ return Number ( fs . readFileSync ( fileName , { encoding : 'utf-8' } ) ) ;
207
+ }
208
+ catch ( err ) {
209
+ return 0 ;
210
+ }
211
+ }
212
+
213
+ writeIntToFile ( fileName , value ) {
214
+ fs . writeFileSync ( fileName , value . toString ( ) , { encoding : 'utf-8' } ) ;
215
+ }
216
+
130
217
async checkDataCache ( problemId ) {
131
- const cacheDir = path . join ( this . config . cache_dir , problemId ) ;
132
- if ( ! fs . existsSync ( cacheDir ) ) {
133
- fs . mkdirSync ( cacheDir , { recursive : true } ) ;
134
- fs . writeFileSync ( path . join ( cacheDir , 'last_sync' ) , '0' ) ;
218
+ const problemCache = path . join ( this . config . cache_dir , 'problem' , problemId ) ;
219
+ let lastSync = 0 ;
220
+ const lastUpdate = ( await this . axios . get ( 'judge/check_data' , {
221
+ params : { problem_id : problemId }
222
+ } ) ) . data . data . last_update ;
223
+ if ( ! fs . existsSync ( problemCache ) ) {
224
+ fs . mkdirSync ( problemCache ) ;
225
+ }
226
+ else {
227
+ lastSync = this . readIntFromFile ( path . join ( problemCache , 'last_sync' ) ) ;
228
+ }
229
+ this . logger . log ( 'Data update time: ' , lastUpdate , '/' , lastSync ) ;
230
+ if ( lastUpdate > lastSync ) {
135
231
await this . fetchData ( problemId ) ;
232
+ this . writeIntToFile ( path . join ( problemCache , 'last_sync' ) , lastUpdate ) ;
233
+ }
234
+ }
235
+
236
+ async checkChecker ( problemId ) {
237
+ const problemCache = path . join ( this . config . cache_dir , 'problem' , problemId ) ;
238
+ const problemConfig = yaml . parse ( fs . readFileSync ( path . join ( problemCache , 'config.yml' ) , { encoding : 'utf-8' } ) ) ;
239
+ let checkerId = problemId ;
240
+ if ( problemConfig . mode != 'spj' ) {
241
+ checkerId = problemConfig . mode ;
242
+ }
243
+
244
+ const checkerDir = path . join ( this . config . cache_dir , 'checker' , checkerId ) ;
245
+ let lastCompile = 0 ;
246
+ const lastSync = this . readIntFromFile ( path . join ( problemCache , 'last_sync' ) ) ;
247
+ if ( ! fs . existsSync ( checkerDir ) ) {
248
+ fs . mkdirSync ( checkerDir ) ;
136
249
}
137
250
else {
138
- const lastSync = Number ( fs . readFileSync ( path . join ( cacheDir , 'last_sync' ) , { encoding : 'utf-8' } ) ) ;
139
- const lastUpdate = ( await this . axios . get ( 'judge/check_data' , {
140
- params : { problem_id : problemId }
141
- } ) ) . data . data . last_update ;
142
- this . logger . log ( 'Data update time: ' , lastUpdate , '/' , lastSync ) ;
143
- if ( lastUpdate > lastSync ) {
144
- await this . fetchData ( problemId ) ;
251
+ lastCompile = this . readIntFromFile ( path . join ( checkerDir , 'last_compile' ) ) ;
252
+ }
253
+ this . logger . log ( 'Checker update time: ' , lastCompile , '/' , lastSync ) ;
254
+ if ( lastSync > lastCompile ) {
255
+ if ( problemId === checkerId ) {
256
+ await this . compileChecher ( checkerId , false ) ;
257
+ }
258
+ else {
259
+ await this . compileChecher ( checkerId , true ) ;
145
260
}
261
+ this . writeIntToFile ( path . join ( checkerDir , 'last_compile' ) , lastSync ) ;
146
262
}
263
+ return checkerId ;
264
+ }
265
+
266
+ async compileChecher ( checkerId , builtin ) {
267
+ let result ;
268
+ this . setupWorkingDirectory ( path . join ( this . config . cache_dir , 'checker' , checkerId ) , false ) ;
269
+ const srcPath = path . join ( this . config . cache_dir , 'checker' , checkerId , 'source' , 'checker.cpp' ) ;
270
+ if ( builtin ) {
271
+ fs . copyFileSync ( path . join ( this . config . builtin_checker , checkerId + '.cpp' ) , srcPath ) ;
272
+ }
273
+ else {
274
+ fs . copyFileSync ( path . join ( this . config . cache_dir , 'problem' , checkerId , 'checker.cpp' ) , srcPath ) ;
275
+ }
276
+ result = await Judge . compileChecker ( {
277
+ sandboxDirectory : this . config . sandbox_dir ,
278
+ workingDirectory : path . join ( this . config . cache_dir , 'checker' , checkerId ) ,
279
+ sourceName : 'checker.cpp' ,
280
+ type : 'testlib'
281
+ } ) ;
282
+ console . log ( result ) ;
283
+ return result ;
147
284
}
148
285
}
149
286
0 commit comments