1use std::io::{Error, ErrorKind};
2use std::time::Duration;
3use std::{process::Stdio, sync::Arc};
4
5use log::{debug, error, info, warn};
7use serde_json::Value;
8use similar::{ChangeTag, TextDiff};
9use tokio::fs::{self, File};
10use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
11use tokio::process::Command;
12use tokio::time::{timeout, Instant};
13
14use super::IoEvent;
15use crate::app::{App, Data};
16
17const DB_PATH: &str = "./data.json";
18
19pub struct IoAsyncHandler {
21 app: Arc<tokio::sync::Mutex<App>>,
22}
23
24impl IoAsyncHandler {
25 pub fn new(app: Arc<tokio::sync::Mutex<App>>) -> Self {
26 Self { app }
27 }
28
29 pub async fn handle_io_event(&mut self, io_event: IoEvent) {
31 let result = match io_event {
32 IoEvent::Initialize => self.do_initialize().await,
33 IoEvent::RunTest(index, exec) => self.run_test(index, exec).await,
34 IoEvent::RunAll(size) => self.run_all(size).await,
35 IoEvent::RunFailed(indexes) => self.run_failed(indexes).await,
36 IoEvent::SaveData(data) => self.save_data(data).await,
37 IoEvent::LoadChecksyle => self.load_cs().await,
38 IoEvent::Make => self.run_make().await,
39 IoEvent::UpdateRef => self.update_ref().await,
40 IoEvent::SendVMChecker => self.send_vmchecker().await,
41 IoEvent::LoadVMChecker => self.load_vmchecker().await,
42 };
43
44 self.update_ref().await.unwrap();
45
46 if let Err(Some(output)) = result {
47 error!("Oops, something wrong happen: \n{}", output.to_string());
48 }
49 }
50
51 async fn do_initialize(&mut self) -> Result<(), Option<Error>> {
53 let mut app = self.app.lock().await;
54 app.initialized(); self.run_make().await?;
58
59 Ok(())
60 }
61
62 async fn load_vmchecker(&self) -> Result<(), Option<Error>> {
63 let client = reqwest::Client::new();
64 let cookie;
65
66 let (username, password) = Self::get_credentials().await?;
67
68 match client
69 .post("https://vmchecker.cs.pub.ro/services/services.py/login")
70 .body(format!("username={}&password={}", username, password))
71 .send()
72 .await
73 {
74 Ok(res) => {
75 cookie = res.headers().get("set-cookie").unwrap().clone();
76
77 if let Ok(body) = res.text().await {
78 if body.contains("false") {
79 return Err(Some(Error::new(ErrorKind::Other, body)));
80 }
81 }
82 }
83 Err(err) => {
84 return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
85 }
86 };
87
88 match client.get("https://vmchecker.cs.pub.ro/services/services.py/getResults?courseId=SD&assignmentId=3-mk-kNN")
89 .header("cookie", &cookie)
90 .send().await {
91 Ok(res) => {
92 let v: Value = serde_json::from_str(&res.text().await.unwrap()).unwrap();
93
94 let mut app = self.app.lock().await;
95
96 match v.get(5) {
97 Some(output) => {
98 app.vmchecker_out.clear();
99 app.vmchecker_out.push_str(&output["Execuția testelor (stdout)"].to_string());
100 },
101 None => {
102 app.vmchecker_out.clear();
103 app.vmchecker_out.push_str(&v[2].to_string());
104 },
105 }
106 },
107 Err(err) => {
108 return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
109 },
110 };
111
112 Ok(())
113 }
114
115 async fn send_vmchecker(&self) -> Result<(), Option<Error>> {
116 let client = reqwest::Client::new();
117 let cookie;
118
119 let (username, password) = Self::get_credentials().await?;
120
121 match client
122 .post("https://vmchecker.cs.pub.ro/services/services.py/login")
123 .body(format!("username={}&password={}", username, password))
124 .send()
125 .await
126 {
127 Ok(res) => {
128 cookie = res.headers().get("set-cookie").unwrap().clone();
129
130 if let Ok(body) = res.text().await {
131 if body.contains("false") {
132 return Err(Some(Error::new(ErrorKind::Other, body)));
133 }
134 }
135 }
136 Err(err) => {
137 return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
138 }
139 };
140
141 let mut make = Command::new("make");
142 make.arg("pack");
143
144 let res = make.output().await?;
145
146 if let Some(code) = res.status.code() {
147 if code != 0 {
148 return Err(Some(Error::new(
149 ErrorKind::Other,
150 std::str::from_utf8(&res.stderr).unwrap(),
151 )));
152 }
153 info!("\n{}", String::from_utf8(res.stdout).unwrap());
154 }
155
156 let body = Self::build_request().await;
157
158 match client.post("https://vmchecker.cs.pub.ro/services/services.py/uploadAssignment")
159 .header("cookie", &cookie)
160 .header("Content-Type", "multipart/form-data; boundary=---------------------------27347846016281380843096153774")
161 .body(body)
162 .send().await {
163 Ok(res) => {
164 info!("{:?}", res.text().await);
165
166 info!("Uploaded file to VMChecker, waiting for results");
167 },
168 Err(err) => {
169 return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
170 },
171 };
172
173 Ok(())
174 }
175
176 async fn get_credentials() -> Result<(String, String), Option<Error>> {
177 let mut username = String::new();
178 let mut password = String::new();
179 let mut buf = String::new();
180
181 match File::open(".env").await {
182 Ok(file) => {
183 let mut bufread = BufReader::new(file);
184 bufread.read_line(&mut buf).await.unwrap();
185
186 let cred: Vec<&str> = buf.split('=').collect();
187 username.push_str(cred[1]);
188
189 buf.clear();
190 bufread.read_line(&mut buf).await.unwrap();
191
192 let cred: Vec<&str> = buf.split('=').collect();
193 password.push_str(cred[1]);
194 }
195 Err(_) => {
196 let mut file = File::create(".env").await.unwrap();
197 file.write(String::from("username=\npassword=\n").as_bytes())
198 .await
199 .unwrap();
200 return Err(Some(Error::new(
201 ErrorKind::Other,
202 "No env file found, creating... Please input your credentials",
203 )));
204 }
205 }
206
207 Ok((
208 String::from(username.strip_suffix('\n').unwrap()),
209 String::from(password.strip_suffix('\n').unwrap()),
210 ))
211 }
212
213 async fn build_request() -> Vec<u8> {
214 let mut body: Vec<u8> = Vec::new();
215
216 let mut paths = fs::read_dir("./").await.unwrap();
217 let mut zip = String::new();
218
219 while let Ok(Some(path)) = paths.next_entry().await {
220 if path.path().display().to_string().contains("Tema3.zip") {
221 zip.push_str(&path.path().display().to_string());
222 }
223 }
224
225 let mut zip_buffer = BufReader::new(File::open(&zip).await.unwrap());
226
227 body.append(&mut "-----------------------------27347846016281380843096153774\r\n".into());
229 body.append(&mut format!("Content-Disposition: form-data; name=\"archiveFile\"; filename=\"{}\"\r\nContent-Type: application/x-zip-compressed\r\n\r\n", zip).into());
230
231 loop {
232 if let Ok(byte) = zip_buffer.read_u8().await {
233 body.push(byte);
234 } else {
235 break;
236 }
237 }
238
239 body.push(b'\r');
240 body.push(b'\n');
241
242 body.append(&mut "-----------------------------27347846016281380843096153774\r\n".into());
244 body.append(&mut "Content-Disposition: form-data; name=\"courseId\"\r\n\r\nSD\r\n".into());
245
246 body.append(&mut "-----------------------------27347846016281380843096153774\r\n".into());
248 body.append(
249 &mut "Content-Disposition: form-data; name=\"assignmentId\"\r\n\r\n3-mk-kNN\r\n".into(),
250 );
251 body.append(&mut "-----------------------------27347846016281380843096153774--\r\n".into());
252
253 body
254 }
255
256 async fn update_ref(&self) -> Result<(), Option<Error>> {
257 let mut app = self.app.lock().await;
258
259 let index = app.test_list_state.selected().unwrap();
260 let (test_index, exec_index) = (
261 index % app.test_list[0].len(),
262 index / app.test_list[0].len(),
263 );
264
265 app.current_ref = fs::read_to_string(format!(
266 "{}ref/{:02}-{}.ref",
267 app.test_path, app.test_list[exec_index][test_index].id, app.exec_name[exec_index]
268 ))
269 .await?;
270
271 app.diff =
272 TextDiff::from_lines(&app.current_ref, &app.test_list[exec_index][test_index].log)
273 .iter_all_changes()
274 .map(|item| {
275 let sign = match item.tag() {
276 ChangeTag::Delete => "-",
277 ChangeTag::Insert => "+",
278 ChangeTag::Equal => " ",
279 };
280
281 match item.missing_newline() {
282 true => (sign, format!("{}", item)),
283 false => (sign, format!("{}⏎", item)),
284 }
285 })
286 .collect();
287
288 Ok(())
289 }
290
291 async fn save_data(&mut self, data: Data) -> Result<(), Option<Error>> {
292 debug!("Saving data");
293 tokio::fs::write(DB_PATH, serde_json::to_string_pretty(&data).unwrap()).await?;
294 Ok(())
295 }
296
297 async fn load_cs(&mut self) -> Result<(), Option<Error>> {
298 info!("Running checkstyle");
299
300 let mut app = self.app.lock().await;
301
302 let mut cs = Command::new(format!("{}/cs/cs.sh", app.test_path));
303 cs.arg(".");
304
305 let output = cs.output().await?.stdout;
306
307 app.checkstyle.clear();
308 app.checkstyle
309 .push_str(std::str::from_utf8(&output).unwrap());
310
311 let mut out_file = File::create(format!("{}checkstyle.txt", app.test_path)).await?;
312
313 out_file.write_all(&output).await?;
314
315 Ok(())
316 }
317
318 async fn run_make(&self) -> Result<(), Option<Error>> {
319 info!("Running makefile");
320 let mut make = Command::new("make");
321 let make_run = make.arg("build");
322 let res = make_run.output().await?;
323
324 if let Some(code) = res.status.code() {
325 if code != 0 {
326 return Err(Some(Error::new(
327 ErrorKind::Other,
328 std::str::from_utf8(&res.stderr).unwrap(),
329 )));
330 }
331 info!("\n{}", String::from_utf8(res.stdout).unwrap());
332 }
333
334 Ok(())
335 }
336
337 async fn run_all(&mut self, size: usize) -> Result<(), Option<Error>> {
338 let mut threads = Vec::new();
339
340 self.run_make().await?;
341
342 for index in 0..size {
343 let copy = Arc::clone(&self.app);
344
345 let thread = tokio::spawn(async move {
346 debug!("Waiting on mutex");
347 let mut app = copy.lock().await;
348
349 let (test_index, exec_index) = (
350 index % app.test_list[0].len(),
351 index / app.test_list[0].len(),
352 );
353
354 app.test_list[exec_index][test_index].status.clear();
355 app.test_list[exec_index][test_index]
356 .status
357 .push_str("STARTING");
358 app.dispatch(IoEvent::RunTest(test_index, exec_index)).await;
359 });
360
361 threads.push(thread);
362 }
363
364 for thread in threads {
365 thread.await.unwrap();
366 }
367
368 Ok(())
369 }
370
371 async fn run_failed(&self, indexes: Vec<(usize, usize)>) -> Result<(), Option<Error>> {
372 let mut threads = Vec::new();
373
374 self.run_make().await?;
375
376 for index in indexes {
377 let copy = Arc::clone(&self.app);
378
379 let thread = tokio::spawn(async move {
380 debug!("Waiting on mutex");
381 let mut app = copy.lock().await;
382
383 let (test_index, exec_index) = index;
384
385 app.test_list[exec_index][test_index].status.clear();
386 app.test_list[exec_index][test_index]
387 .status
388 .push_str("STARTING");
389 app.dispatch(IoEvent::RunTest(test_index, exec_index)).await;
390 });
391
392 threads.push(thread);
393 }
394
395 for thread in threads {
396 thread.await.unwrap();
397 }
398
399 Ok(())
400 }
401
402 async fn run_test(&self, index: usize, exec: usize) -> Result<(), Option<Error>> {
410 let mut app = self.app.lock().await;
411
412 let app_name = String::from(&app.exec_name[exec]);
413
414 let mut out_file = File::create(format!(
415 "{}output/{:02}-{}.out",
416 app.test_path, index, app_name
417 ))
418 .await?;
419
420 let valgrind = app.valgrind_enabled;
421
422 let ref_prom = fs::read(format!(
423 "{}ref/{:02}-{}.ref",
424 app.test_path, index, app_name
425 ));
426
427 let ref_file = match ref_prom.await {
428 Ok(f1) => f1,
429 Err(a) => {
430 error!(
431 "Cannot find {}",
432 format!("{}input/{:02}-{}.ref", app.test_path, index, app_name)
433 );
434
435 let current_test = &mut app.test_list[exec][index];
436 current_test.status.clear();
437 current_test.status.push_str("ERROR");
438 return Err(Some(a));
439 }
440 };
441
442 let mut binding: Command;
443 if valgrind {
444 binding = Command::new("valgrind");
445 binding
446 .arg(format!(
447 "--log-file={}output/{:02}-{}.valgrind",
448 app.test_path, index, app_name
449 ))
450 .arg("--leak-check=full")
451 .arg("--track-origins=yes")
452 .arg("--show-leak-kinds=all")
453 .arg("--error-exitcode=69")
454 .arg(format!("./{}", app_name));
455 } else {
456 binding = Command::new(format!("./{}", app_name));
457 }
458
459 let current_test = &mut app.test_list[exec][index];
460 current_test.status.clear();
461 current_test.status.push_str("RUNNING");
462 let timelimit = current_test.timeout;
463
464 info!(
465 "Running test number {} with status {}",
466 index, current_test.status
467 );
468 let in_file = std::fs::File::open(format!(
469 "{}input/{:02}-{}.in",
470 app.test_path, index, app_name
471 ))?;
472 drop(app);
473
474 let run = binding.stdin(in_file).stdout(Stdio::piped());
475
476 debug!("Executing {:?}", run);
477 match run.spawn() {
478 Ok(mut child) => {
479 debug!("{:?}", child);
480
481 let mut log = String::new();
482 let mut res = String::new();
483 let start = Instant::now();
484
485 debug!("Finished input, waiting for stdout");
486
487 if let Some(ref mut stdout) = child.stdout {
488 let mut lines = BufReader::new(stdout).lines();
489
490 loop {
491 if let Ok(res_line) =
492 timeout(Duration::from_millis(timelimit), lines.next_line()).await
493 {
494 if let Ok(Some(line)) = res_line {
495 let l: String = format!("{}\n", line);
496 debug!("file_contains {}", l);
497 log.push_str(&l);
498 } else {
499 debug!("Finished reading from stdout");
500 break;
501 }
502
503 if start.elapsed().as_millis() > timelimit as u128 {
504 warn!("timeout");
505 res.push_str("TIMEOUT");
506
507 let mut app = self.app.lock().await;
508 let current_test = &mut app.test_list[exec][index];
509 current_test.status.clear();
510 current_test.status.push_str(&res);
511 current_test.log.clear();
512
513 if valgrind {
514 current_test.time_valgrind = start.elapsed().as_secs_f64();
515 } else {
516 current_test.time_normal = start.elapsed().as_secs_f64();
517 }
518
519 child.kill().await?;
520
521 return Ok(());
522 }
523 } else {
524 warn!("timeout");
525 res.push_str("TIMEOUT");
526
527 let mut app = self.app.lock().await;
528 let current_test = &mut app.test_list[exec][index];
529 current_test.status.clear();
530 current_test.status.push_str(&res);
531 current_test.log.clear();
532
533 if valgrind {
534 current_test.time_valgrind = start.elapsed().as_secs_f64();
535 } else {
536 current_test.time_normal = start.elapsed().as_secs_f64();
537 }
538
539 child.kill().await?;
540
541 return Ok(());
542 }
543 }
544 } else {
545 warn!("I think it crashed");
546 }
547
548 debug!("time here is {}", start.elapsed().as_secs_f64());
549
550 if let Ok(out) = child.wait_with_output().await {
551 debug!("exit status {:?}", out.status.code());
552 if out.status.code().is_none() {
553 let runtime = start.elapsed().as_secs_f64();
554
555 log.push_str(&out.status.to_string().split_off(8));
556 res.push_str("CRASHED");
557
558 let mut app = self.app.lock().await;
559 let current_test = &mut app.test_list[exec][index];
560
561 current_test.status.clear();
562 current_test.status.push_str(&res);
563 current_test.log.clear();
564 current_test.log.push_str(&log);
565
566 if valgrind {
567 current_test.time_valgrind = runtime;
568 } else {
569 current_test.time_normal = runtime;
570 }
571
572 app.unwritten_data = true;
573 return Ok(());
574 } else if let Some(69) = out.status.code() {
575 let runtime = start.elapsed().as_secs_f64();
576
577 log.push_str("Check output folder for valgrind errors");
578 res.push_str("MEMLEAKS");
579
580 let mut app = self.app.lock().await;
581 let current_test = &mut app.test_list[exec][index];
582
583 current_test.status.clear();
584 current_test.status.push_str(&res);
585 current_test.log.clear();
586 current_test.log.push_str(&log);
587
588 if valgrind {
589 current_test.time_valgrind = runtime;
590 } else {
591 current_test.time_normal = runtime;
592 }
593
594 app.unwritten_data = true;
595 return Ok(());
596 }
597 } else {
598 error!("Oops");
599 }
600
601 let correct: bool = if log == std::str::from_utf8(&ref_file).unwrap() {
602 true
603 } else {
604 res.push('0');
605 false
606 };
607
608 let runtime = start.elapsed().as_secs_f64();
609 debug!("time={:5}", runtime);
610
611 out_file.write_all(log.as_bytes()).await?;
612
613 let mut app = self.app.lock().await;
614 let current_test = &mut app.test_list[exec][index];
615
616 if valgrind {
617 current_test.time_valgrind = runtime;
618 } else {
619 current_test.time_normal = runtime;
620 }
621
622 if correct {
623 res.push_str(¤t_test.test_score.to_string());
624 }
625
626 current_test.status.clear();
627 current_test.status.push_str(&res);
628 current_test.log.clear();
629 current_test.log.push_str(&log);
630
631 app.unwritten_data = true;
632 }
633 Err(error) => {
634 let mut app = self.app.lock().await;
635 let current_test = &mut app.test_list[exec][index];
636
637 current_test.status.clear();
638 current_test.status.push_str("ERROR");
639
640 current_test.log.clear();
641 current_test.log.push_str(&error.to_string());
642
643 app.unwritten_data = true;
644 warn!("{:?}", error);
645 }
646 }
647
648 Ok(())
649 }
650}