Completed
Push — master ( 3d3760...b0d42a )
by John
30s queued 13s
created

app/Jobs/AntiCheat.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace App\Jobs;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Bus\Queueable;
7
use Illuminate\Queue\SerializesModels;
8
use Illuminate\Queue\InteractsWithQueue;
9
use Illuminate\Contracts\Queue\ShouldQueue;
10
use Illuminate\Foundation\Bus\Dispatchable;
11
use Symfony\Component\Process\Exception\ProcessFailedException;
12
use App\Models\Eloquent\Contest as EloquentContestModel;
13
use App\Models\Eloquent\User;
14
use Imtigger\LaravelJobStatus\Trackable;
15
use Symfony\Component\Process\Process;
16
use KubAT\PhpSimple\HtmlDomParser;
17
use PhpZip\ZipFile;
18
use Storage;
19
use Str;
20
use Log;
21
22
class AntiCheat implements ShouldQueue
23
{
24
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Trackable;
25
26
    public $tries=1;
27
    public $progressVal=40;
28
    public $stepVal=0;
29
    protected $cid;
30
    protected $retArr=[];
31
    protected $supportLang=[
32
        'c'=>'c',
33
        'cpp'=>'c++',
34
        'java'=>'java'
35
    ];
36
37
    /**
38
     * Create a new job instance.
39
     *
40
     * @return void
41
     */
42
43
    public function __construct($cid)
44
    {
45
        $this->prepareStatus();
46
        $this->cid=$cid;
47
        $this->setProgressMax(100);
48
    }
49
50
    /**
51
     * Execute the job.
52
     *
53
     * @return void
54
     */
55
    public function handle()
56
    {
57
        $cid=$this->cid;
58
        $contest=EloquentContestModel::find($cid);
59
60
        if (!$contest->isJudgingComplete()) {
61
            throw new Exception('Judging Incompleted');
0 ignored issues
show
The type App\Jobs\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
62
        }
63
64
        $acceptedSubmissions=$contest->submissions->whereIn('verdict', [
65
            'Accepted',
66
            'Partially Accepted'
67
        ]);
68
69
        $probIndex=$contest->problems->pluck('ncode', 'pid')->all();
70
        Storage::deleteDirectory("contest/anticheat/$cid/");
71
        sleep(1);
72
        $this->setProgressNow(20);
73
        $totProb=0;
74
        $probLangs=[];
75
76
77
        foreach ($acceptedSubmissions as $submission) {
78
            $lang=$submission->compiler->lang;
79
            if (Arr::has($this->supportLang, $lang)) {
80
                $prob=$probIndex[$submission->pid];
81
                $lang=$this->supportLang[$lang];
82
                $ext=$lang;
83
                Storage::put("contest/anticheat/$cid/raw/$prob/$lang/[$submission->uid][$submission->sid].$ext", $submission->solution);
84
                if (!isset($probLangs[$prob][$lang])) {
85
                    $probLangs[$prob][$lang]=1;
86
                } else {
87
                    $probLangs[$prob][$lang]++;
88
                }
89
                $totProb++;
90
            }
91
        }
92
93
        $this->setProgressNow(40);
94
        $this->stepVal=50 / ($totProb * 2);
95
        $this->progressVal=40;
96
97
        foreach ($probLangs as $prob=>$langs) {
98
            foreach ($langs as $lang=>$submissionCount) {
99
                $this->detectPlagiarism([
100
                    'lang'=>$lang,
101
                    'cid'=>$cid,
102
                    'prob'=>$prob,
103
                    'count'=>$submissionCount,
104
                    'comment'=>"Contest #$cid Problem $prob Language $lang Code Plagiarism Check",
105
                ]);
106
            }
107
        }
108
        $this->setProgressNow(90);
109
        $this->finalizeReport();
110
        $this->setProgressNow(100);
111
    }
112
113
    private function incProgress($factor=1) {
114
        $this->progressVal+=($this->stepVal) * $factor;
115
        $this->setProgressNow(intval($this->progressVal));
116
    }
117
118
    private function detectPlagiarism($config)
119
    {
120
        $lang=$config['lang'];
121
        $cid=$config['cid'];
122
        $prob=$config['prob'];
123
        $count=$config['count'];
124
        if (strtoupper(substr(php_uname('s'), 0, 3))==='WIN') {
125
            // Windows
126
            $exe=base_path('binary'.DIRECTORY_SEPARATOR.'win'.DIRECTORY_SEPARATOR.'sim_'.$lang.'.exe');
127
        } else {
128
            // Linux or else
129
            $process=new Process(['chmod', '+x', base_path('binary'.DIRECTORY_SEPARATOR.'linux'.DIRECTORY_SEPARATOR.'sim*')]);
130
            $process->run();
131
            $exe=base_path('binary'.DIRECTORY_SEPARATOR.'linux'.DIRECTORY_SEPARATOR.'sim_'.$lang);
132
        }
133
134
        $exec=escapeshellarg($exe).' -p ';
135
136
        // wildcardly add all files
137
        $exec.='*.'.$lang;
138
139
        $process=new Process($exec);
140
        $process->setWorkingDirectory(Storage::path('contest'.DIRECTORY_SEPARATOR.'anticheat'.DIRECTORY_SEPARATOR.$cid.DIRECTORY_SEPARATOR.'raw'.DIRECTORY_SEPARATOR.$prob.DIRECTORY_SEPARATOR.$lang));
141
        $process->run();
142
        if (!$process->isSuccessful()) {
143
            Log::error("Cannot Compare Problem $prob of Contest $cid, Languages $lang");
144
            throw new ProcessFailedException($process);
145
        }
146
        Log::info($process->getOutput());
147
        $this->incProgress($count);
148
        //afterWork
149
        $this->afterWork($cid, $prob, $lang, $process->getOutput());
150
        $this->incProgress($count);
151
    }
152
153
    private function afterWork($cid, $prob, $lang, $rawContent)
154
    {
155
        foreach (preg_split('~[\r\n]+~', $rawContent) as $line) {
156
            if (blank($line) or ctype_space($line)) {
157
                continue;
158
            }
159
            // [3057][64659].c++ consists for 100 % of [3057][64679].c++ material
160
            $line=explode('%', $line);
161
            if (!isset($line[1])) {
162
                continue;
163
            }
164
            [$uid1, $sid1, $percentage]=sscanf($line[0], "[%d][%d].$lang consists for %d ");
165
            [$uid2, $sid2]=sscanf($line[1], " of [%d][%d].$lang material");
166
            if ($uid1==$uid2) {
167
                continue;
168
            }
169
            $username1=User::find($uid1)->name;
170
            $username2=User::find($uid2)->name;
171
            $this->retArr[]=[
172
                "sub1"=>"$sid1. [$prob][$username1]",
173
                "sub2"=>"$sid2. [$prob][$username2]",
174
                "similarity"=>$percentage,
175
                "code1"=>Storage::disk('local')->get("contest/anticheat/$cid/raw/$prob/$lang/[$uid1][$sid1].$lang"),
176
                "code2"=>Storage::disk('local')->get("contest/anticheat/$cid/raw/$prob/$lang/[$uid2][$sid2].$lang"),
177
                'cid'=>$cid,
178
                'prob'=>$prob,
179
                'lang'=>$lang,
180
            ];
181
            Log::info($line);
182
        }
183
    }
184
185
    private function finalizeReport()
186
    {
187
        $retArr=$this->retArr;
188
        usort($retArr, function($a, $b) {
189
            return $b['similarity']<=>$a['similarity'];
190
        });
191
        Log::debug($retArr);
192
        $cid=$this->cid;
193
        $index=0;
194
        $generalPage="<table><tr><th>Language</th><th>Submission 1</th><th>Submission 2</th><th>Sub 1 Consists for x% of Sub 2</th></tr>";
195
        foreach ($retArr as $ret) {
196
            $lang=strtoupper($ret['lang']);
197
            $sub1=$ret['sub1'];
198
            $sub2=$ret['sub2'];
199
            $similarity=$ret['similarity'];
200
            $generalPage.="
201
                <tr>
202
                    <td>$lang</td>
203
                    <td><a href=\"match$index.html\">$sub1</a></td>
204
                    <td><a href=\"match$index.html\">$sub2</a></td>
205
                    <td align=right>$similarity%</td>
206
                </tr>
207
            ";
208
            //write match$index.html
209
            $matchIndexPage='<frameset cols="50%,50%" rows="100%"><frame src="match'.$index.'-0.html" name="0"><frame src="match'.$index.'-1.html" name="1"></frameset>';
210
            Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index.html", $matchIndexPage);
211
            //write two code html
212
            $match0Page='<pre>'.PHP_EOL.htmlspecialchars($ret['code1']).PHP_EOL.'</pre>';
213
            Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index-0.html", $match0Page);
214
            $match1Page='<pre>'.PHP_EOL.htmlspecialchars($ret['code2']).PHP_EOL.'</pre>';
215
            Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index-1.html", $match1Page);
216
            $index++;
217
        }
218
        $generalPage.="</table>";
219
        Storage::disk('local')->put("contest/anticheat/$cid/report/final/index.html", $generalPage);
220
        $zip=new ZipFile();
221
        $zip->addDir(storage_path("app/contest/anticheat/$cid/report/final/"))->saveAsFile(storage_path("app/contest/anticheat/$cid/report/report.zip"))->close();
222
    }
223
224
    public function failed()
225
    {
226
227
    }
228
}
229