1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Console\Commands;
7
8use App\Models\Beatmap;
9use App\Models\Score\Best;
10use App\Models\UserStatistics;
11use Exception;
12use Illuminate\Console\Command;
13
14class UserRecalculateRankCounts extends Command
15{
16 private ?int $from;
17 private ?int $until;
18
19 /**
20 * The name and signature of the console command.
21 *
22 * @var string
23 */
24 protected $signature = 'user:recalculate-rank-counts {--from=} {--until=}';
25
26 /**
27 * The console command description.
28 *
29 * @var string
30 */
31 protected $description = 'Recalculate rank counts for user statistics.';
32
33 /**
34 * Execute the console command.
35 *
36 * @return mixed
37 */
38 public function handle()
39 {
40 $this->from = get_int($this->option('from'));
41 $this->until = get_int($this->option('until'));
42
43 $continue = $this->confirm('This will recalculate and update the rank counts for user statistics, continue?');
44
45 if (!$continue) {
46 $this->error('User aborted!');
47 return;
48 }
49
50 $start = time();
51
52 foreach (Beatmap::MODES as $mode => $id) {
53 $this->processMode($mode);
54 }
55
56 $this->warn("\n".(time() - $start).'s taken.');
57 }
58
59 private function processMode($mode)
60 {
61 $this->info("Recalculating {$mode}");
62 $class = UserStatistics::class.'\\'.studly_case($mode);
63 $query = $class::query();
64 if (present($this->from)) {
65 $query->where('user_id', '>=', $this->from);
66 }
67
68 if (present($this->until)) {
69 $query->where('user_id', '<=', $this->until);
70 }
71
72 $count = $query->count();
73 $bar = $this->output->createProgressBar($count);
74
75 $query->chunkById(1000, function ($chunk) use ($bar) {
76 foreach ($chunk as $stats) {
77 try {
78 $counts = $this->getCountsWithStats($stats);
79 $stats->update([
80 'x_rank_count' => $counts['X'],
81 'xh_rank_count' => $counts['XH'],
82 's_rank_count' => $counts['S'],
83 'sh_rank_count' => $counts['SH'],
84 'a_rank_count' => $counts['A'],
85 ]);
86
87 $bar->advance();
88 } catch (Exception $e) {
89 $this->error("Exception caught, user_id: {$stats->user_id}");
90 $this->error($e->getMessage());
91 }
92 }
93 }, 'user_id');
94
95 $bar->finish();
96 $this->info('');
97 }
98
99 private function getCountsWithStats($stats)
100 {
101 $class = Best::class.'\\'.get_class_basename(get_class($stats));
102 $counts = $class::where('user_id', '=', $stats->user_id)
103 ->accurateRankCounts()[$stats->user_id] ?? [];
104
105 return $this->map($counts);
106 }
107
108 private function map($values)
109 {
110 return [
111 'XH' => $values['XH'] ?? 0,
112 'SH' => $values['SH'] ?? 0,
113 'X' => $values['X'] ?? 0,
114 'S' => $values['S'] ?? 0,
115 'A' => $values['A'] ?? 0,
116 ];
117 }
118}