AntiCheat::handle()   B
last analyzed

Complexity

Conditions 7
Paths 13

Size

Total Lines 56
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 39
nc 13
nop 0
dl 0
loc 56
rs 8.3626
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
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 Exception;
20
use Str;
21
use Log;
22
23
class AntiCheat implements ShouldQueue
24
{
25
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Trackable;
0 ignored issues
show
introduced by
The trait Illuminate\Queue\SerializesModels requires some properties which are not provided by App\Jobs\AntiCheat: $id, $relations, $class, $keyBy
Loading history...
Bug introduced by
The trait Imtigger\LaravelJobStatus\Trackable requires the property $id which is not provided by App\Jobs\AntiCheat.
Loading history...
26
27
    public $tries=1;
28
    public $progressVal=40;
29
    public $stepVal=0;
30
    protected $cid;
31
    protected $retArr=[];
32
    protected $supportLang=[
33
        'c'=>'c',
34
        'cpp'=>'c++',
35
        'java'=>'java'
36
    ];
37
38
    /**
39
     * Create a new job instance.
40
     *
41
     * @return void
42
     */
43
44
    public function __construct($cid)
45
    {
46
        $this->prepareStatus();
47
        $this->cid=$cid;
48
        $this->setProgressMax(100);
49
    }
50
51
    /**
52
     * Execute the job.
53
     *
54
     * @return void
55
     */
56
    public function handle()
57
    {
58
        $cid=$this->cid;
59
        $contest=Contest::find($cid);
60
61
        if (!$contest->isJudgingComplete()) {
62
            throw new Exception('Judging Incompleted');
63
        }
64
65
        $acceptedSubmissions=$contest->submissions->whereIn('verdict', [
66
            'Accepted',
67
            'Partially Accepted'
68
        ]);
69
70
        $probIndex=$contest->problems->pluck('ncode', 'pid')->all();
71
        Storage::deleteDirectory("contest/anticheat/$cid/");
72
        sleep(1);
73
        $this->setProgressNow(20);
74
        $totProb=0;
75
        $probLangs=[];
76
77
78
        foreach ($acceptedSubmissions as $submission) {
79
            $lang=$submission->compiler->lang;
80
            if (Arr::has($this->supportLang, $lang)) {
81
                $prob=$probIndex[$submission->pid];
82
                $lang=$this->supportLang[$lang];
83
                $ext=$lang;
84
                Storage::put("contest/anticheat/$cid/raw/$prob/$lang/[$submission->uid][$submission->sid].$ext", $submission->solution);
85
                if (!isset($probLangs[$prob][$lang])) {
86
                    $probLangs[$prob][$lang]=1;
87
                } else {
88
                    $probLangs[$prob][$lang]++;
89
                }
90
                $totProb++;
91
            }
92
        }
93
94
        $this->setProgressNow(40);
95
        $this->stepVal=50 / ($totProb * 2);
96
        $this->progressVal=40;
97
98
        foreach ($probLangs as $prob=>$langs) {
99
            foreach ($langs as $lang=>$submissionCount) {
100
                $this->detectPlagiarism([
101
                    'lang'=>$lang,
102
                    'cid'=>$cid,
103
                    'prob'=>$prob,
104
                    'count'=>$submissionCount,
105
                    'comment'=>"Contest #$cid Problem $prob Language $lang Code Plagiarism Check",
106
                ]);
107
            }
108
        }
109
        $this->setProgressNow(90);
110
        $this->finalizeReport();
111
        $this->setProgressNow(100);
112
    }
113
114
    private function incProgress($factor=1) {
115
        $this->progressVal+=($this->stepVal) * $factor;
116
        $this->setProgressNow(intval($this->progressVal));
117
    }
118
119
    private function detectPlagiarism($config)
120
    {
121
        $lang=$config['lang'];
122
        $cid=$config['cid'];
123
        $prob=$config['prob'];
124
        $count=$config['count'];
125
        if (strtoupper(substr(php_uname('s'), 0, 3))==='WIN') {
126
            // Windows
127
            $exe=base_path('binary'.DIRECTORY_SEPARATOR.'win'.DIRECTORY_SEPARATOR."sim_$lang.exe");
128
        } else {
129
            // Linux or else
130
            $process=new Process(['chmod', '+x', base_path('binary'.DIRECTORY_SEPARATOR.'linux'.DIRECTORY_SEPARATOR.'sim*')]);
131
            $process->run();
132
            $exe=base_path('binary'.DIRECTORY_SEPARATOR.'linux'.DIRECTORY_SEPARATOR."sim_$lang");
133
        }
134
135
        $exec=escapeshellarg($exe).' -p ';
136
137
        // wildcardly add all files
138
        $exec.='*.'.$lang;
139
140
        $process = Process::fromShellCommandline($exec);
141
        $process->setWorkingDirectory(Storage::path('contest'.DIRECTORY_SEPARATOR.'anticheat'.DIRECTORY_SEPARATOR.$cid.DIRECTORY_SEPARATOR.'raw'.DIRECTORY_SEPARATOR.$prob.DIRECTORY_SEPARATOR.$lang));
142
        $process->run();
143
        if (!$process->isSuccessful()) {
144
            Log::error("Cannot Compare Problem $prob of Contest $cid, Languages $lang");
145
            throw new ProcessFailedException($process);
146
        }
147
        Log::info($process->getOutput());
148
        $this->incProgress($count);
149
        //afterWork
150
        $this->afterWork($cid, $prob, $lang, $process->getOutput());
151
        $this->incProgress($count);
152
    }
153
154
    private function afterWork($cid, $prob, $lang, $rawContent)
155
    {
156
        foreach (preg_split('~[\r\n]+~', $rawContent) as $line) {
157
            if (blank($line) or ctype_space($line)) {
158
                continue;
159
            }
160
            // [3057][64659].c++ consists for 100 % of [3057][64679].c++ material
161
            $line=explode('%', $line);
162
            if (!isset($line[1])) {
163
                continue;
164
            }
165
            [$uid1, $sid1, $percentage]=sscanf($line[0], "[%d][%d].$lang consists for %d ");
166
            [$uid2, $sid2]=sscanf($line[1], " of [%d][%d].$lang material");
167
            if ($uid1==$uid2) {
168
                continue;
169
            }
170
            $username1=User::find($uid1)->name;
171
            $username2=User::find($uid2)->name;
172
            $this->retArr[]=[
173
                "sub1"=>"$sid1. [$prob][$username1]",
174
                "sub2"=>"$sid2. [$prob][$username2]",
175
                "similarity"=>$percentage,
176
                "code1"=>Storage::disk('local')->get("contest/anticheat/$cid/raw/$prob/$lang/[$uid1][$sid1].$lang"),
177
                "code2"=>Storage::disk('local')->get("contest/anticheat/$cid/raw/$prob/$lang/[$uid2][$sid2].$lang"),
178
                'cid'=>$cid,
179
                'prob'=>$prob,
180
                'lang'=>$lang,
181
            ];
182
            Log::info($line);
0 ignored issues
show
Bug introduced by
$line of type string[] is incompatible with the type string expected by parameter $message of Illuminate\Support\Facades\Log::info(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

182
            Log::info(/** @scrutinizer ignore-type */ $line);
Loading history...
183
        }
184
    }
185
186
    private function finalizeReport()
187
    {
188
        $retArr=$this->retArr;
189
        usort($retArr, function($a, $b) {
190
            return $b['similarity']<=>$a['similarity'];
191
        });
192
        Log::debug($retArr);
193
        $cid=$this->cid;
194
        $index=0;
195
        $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>";
196
        foreach ($retArr as $ret) {
197
            $lang=strtoupper($ret['lang']);
198
            $sub1=$ret['sub1'];
199
            $sub2=$ret['sub2'];
200
            $similarity=$ret['similarity'];
201
            $generalPage.="
202
                <tr>
203
                    <td>$lang</td>
204
                    <td><a href=\"match$index.html\">$sub1</a></td>
205
                    <td><a href=\"match$index.html\">$sub2</a></td>
206
                    <td align=right>$similarity%</td>
207
                </tr>
208
            ";
209
            //write match$index.html
210
            $matchIndexPage='<frameset cols="50%,50%" rows="100%"><frame src="match'.$index.'-0.html" name="0"><frame src="match'.$index.'-1.html" name="1"></frameset>';
211
            Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index.html", $matchIndexPage);
212
            //write two code html
213
            $match0Page='<pre>'.PHP_EOL.htmlspecialchars($ret['code1']).PHP_EOL.'</pre>';
214
            Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index-0.html", $match0Page);
215
            $match1Page='<pre>'.PHP_EOL.htmlspecialchars($ret['code2']).PHP_EOL.'</pre>';
216
            Storage::disk('local')->put("contest/anticheat/$cid/report/final/match$index-1.html", $match1Page);
217
            $index++;
218
        }
219
        $generalPage.="</table>";
220
        Storage::disk('local')->put("contest/anticheat/$cid/report/final/index.html", $generalPage);
221
        $zip=new ZipFile();
222
        $zip->addDir(storage_path("app/contest/anticheat/$cid/report/final/"))->saveAsFile(storage_path("app/contest/anticheat/$cid/report/report.zip"))->close();
223
    }
224
225
    public function failed()
226
    {
227
228
    }
229
}
230