Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into config-globals
Browse files Browse the repository at this point in the history
  • Loading branch information
nanaya committed Nov 10, 2023
2 parents a69f7db + 1db8536 commit 7c86d47
Show file tree
Hide file tree
Showing 28 changed files with 938 additions and 443 deletions.
83 changes: 54 additions & 29 deletions app/Console/Commands/ModdingRankCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace App\Console\Commands;

use App\Models\Beatmap;
use App\Enums\Ruleset;
use App\Models\Beatmapset;
use Illuminate\Console\Command;

Expand All @@ -16,7 +16,7 @@ class ModdingRankCommand extends Command
*
* @var string
*/
protected $signature = 'modding:rank';
protected $signature = 'modding:rank {--no-wait} {--count-only}';

/**
* The console command description.
Expand All @@ -25,60 +25,81 @@ class ModdingRankCommand extends Command
*/
protected $description = 'Rank maps in queue.';

private bool $countOnly = false;
private bool $noWait = false;

public static function getStats(Ruleset $ruleset)
{
$rankedTodayCount = Beatmapset::ranked()
->withoutTrashed()
->withModesForRanking($ruleset->value)
->where('approved_date', '>=', now()->subDays())
->count();

return [
'availableQuota' => $GLOBALS['cfg']['osu']['beatmapset']['rank_per_day'] - $rankedTodayCount,
'inQueue' => Beatmapset::toBeRanked($ruleset)->count(),
'rankedToday' => $rankedTodayCount,
];
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('Ranking beatmapsets...');
$this->countOnly = get_bool($this->option('count-only'));
$this->noWait = get_bool($this->option('no-wait'));

$modeInts = array_values(Beatmap::MODES);
if ($this->countOnly) {
$this->info('Number of beatmapsets in queue:');
} else {
$this->info('Ranking beatmapsets...');
}

$rulesets = Ruleset::cases();

shuffle($modeInts);
shuffle($rulesets);

foreach ($modeInts as $modeInt) {
foreach ($rulesets as $ruleset) {
$this->waitRandom();
$this->rankAll($modeInt);

if ($this->countOnly) {
$stats = static::getStats($ruleset);
$this->info($ruleset->name);
foreach ($stats as $key => $value) {
$this->line("{$key}: {$value}");
}
$this->newLine();
} else {
$this->rankAll($ruleset);
}
}

$this->info('Done');
}

private function rankAll($modeInt)
private function rankAll(Ruleset $ruleset)
{
$this->info('Ranking beatmapsets with at least mode: '.Beatmap::modeStr($modeInt));
$this->info("Ranking beatmapsets with at least mode: {$ruleset->name}");
$stats = static::getStats($ruleset);

$rankedTodayCount = Beatmapset::ranked()
->withoutTrashed()
->withModesForRanking($modeInt)
->where('approved_date', '>=', now()->subDays())
->count();

$rankableQuota = $GLOBALS['cfg']['osu']['beatmapset']['rank_per_day'] - $rankedTodayCount;

$this->info("{$rankedTodayCount} beatmapsets ranked last 24 hours. Can rank {$rankableQuota} more");
$this->info("{$stats['rankedToday']} beatmapsets ranked last 24 hours. Can rank {$stats['availableQuota']} more");

if ($rankableQuota <= 0) {
if ($stats['availableQuota'] <= 0) {
return;
}

$toRankLimit = min($GLOBALS['cfg']['osu']['beatmapset']['rank_per_run'], $rankableQuota);
$toRankLimit = min($GLOBALS['cfg']['osu']['beatmapset']['rank_per_run'], $stats['availableQuota']);

$toBeRankedQuery = Beatmapset::qualified()
->withoutTrashed()
->withModesForRanking($modeInt)
->where('queued_at', '<', now()->subDays($GLOBALS['cfg']['osu']['beatmapset']['minimum_days_for_rank']));

$rankingQueue = $toBeRankedQuery->count();

$toBeRanked = $toBeRankedQuery
$toBeRanked = Beatmapset::tobeRanked($ruleset)
->orderBy('queued_at', 'ASC')
->limit($toRankLimit)
->get();

$this->info("{$rankingQueue} beatmapset(s) in ranking queue");
$this->info("{$stats['inQueue']} beatmapset(s) in ranking queue");
$this->info("Ranking {$toBeRanked->count()} beatmapset(s)");

foreach ($toBeRanked as $beatmapset) {
Expand All @@ -90,6 +111,10 @@ private function rankAll($modeInt)

private function waitRandom()
{
if ($this->noWait || $this->countOnly) {
return;
}

$delay = rand(5, 120);
$this->info("Pausing for {$delay} seconds...");
sleep($delay);
Expand Down
3 changes: 1 addition & 2 deletions app/Http/Controllers/GroupHistoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public function index()
$query = UserGroupEvent::visibleForUser(auth()->user());

if ($params['group'] !== null) {
// Not `app('groups')->byIdentifier(...)` because that would create the group if not found
$groupId = app('groups')->allByIdentifier()->get($params['group'])?->getKey();
$groupId = app('groups')->byIdentifier($params['group'])?->getKey();

if ($groupId !== null) {
$query->where('group_id', $groupId);
Expand Down
31 changes: 2 additions & 29 deletions app/Libraries/Groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

use App\Models\Group;
use App\Traits\Memoizes;
use Exception;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;

Expand Down Expand Up @@ -60,36 +59,10 @@ public function byIdOrFail(int|string|null $id): Group

/**
* Get a group by its identifier (e.g. "admin").
*
* If the requested group doesn't exist, a new one is created.
*/
public function byIdentifier(string $id): Group
public function byIdentifier(string $id): ?Group
{
$group = $this->allByIdentifier()->get($id);

if ($group === null) {
try {
$group = Group::create([
'group_desc' => '',
'group_name' => $id,
'group_type' => 2,
'identifier' => $id,
'short_name' => $id,
])->fresh();
} catch (Exception $ex) {
if (!is_sql_unique_exception($ex)) {
throw $ex;
}
$group = Group::firstWhere(['identifier' => $id]);
}

// TODO: This shouldn't have to be called here, since it's already
// called by `Group::afterCommit`, but `Group::afterCommit` isn't
// running in tests when creating/saving `Group`s.
$this->resetMemoized();
}

return $group;
return $this->allByIdentifier()->get($id);
}

protected function fetch(): Collection
Expand Down
5 changes: 5 additions & 0 deletions app/Libraries/Mods.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public function assertValidForMultiplayer(int $rulesetId, array $ids, bool $isRe
}
}

public function excludeModsAlwaysValidForSubmission(int $rulesetId, array $modIds): array
{
return array_values(array_filter($modIds, fn ($modId) => !$this->mods[$rulesetId][$modId]['AlwaysValidForSubmission']));
}

public function idsToBitset($ids): int
{
if (!is_array($ids)) {
Expand Down
6 changes: 6 additions & 0 deletions app/Libraries/Search/BeatmapsetQueryParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ public static function parse(?string $query): array
case 'artist':
$option = static::makeTextOption($op, $m['value']);
break;
case 'source':
$option = static::makeTextOption($op, $m['value']);
break;
case 'title':
$option = static::makeTextOption($op, $m['value']);
break;
case 'created':
$option = static::makeDateRangeOption($op, $m['value']);
break;
Expand Down
2 changes: 2 additions & 0 deletions app/Libraries/Search/BeatmapsetSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public function getQuery()
$this->addSimpleFilters($query, $nested);
$this->addCreatorFilter($query, $nested);
$this->addTextFilter($query, 'artist', ['artist', 'artist_unicode']);
$this->addTextFilter($query, 'source', ['source']);
$this->addTextFilter($query, 'title', ['title', 'title_unicode']);

$query->filter([
'nested' => [
Expand Down
2 changes: 2 additions & 0 deletions app/Libraries/Search/BeatmapsetSearchParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ class BeatmapsetSearchParams extends SearchParams
public bool $showFollows = false;
public bool $showRecommended = false;
public bool $showSpotlights = false;
public ?string $source = null;
public ?string $status = null;
public ?string $title = null;
public ?array $statusRange = null;
public ?array $hitLength = null;
public ?array $updated = null;
Expand Down
2 changes: 2 additions & 0 deletions app/Libraries/Search/BeatmapsetSearchRequestParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,10 @@ private function parseQuery(): void
'length' => 'hitLength',
'od' => 'accuracy',
'ranked' => 'ranked',
'source' => 'source',
'stars' => 'difficultyRating',
'status' => 'statusRange',
'title' => 'title',
'updated' => 'updated',
];

Expand Down
15 changes: 14 additions & 1 deletion app/Models/Beatmapset.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace App\Models;

use App\Enums\Ruleset;
use App\Exceptions\BeatmapProcessorException;
use App\Exceptions\InvariantException;
use App\Jobs\CheckBeatmapsetCovers;
Expand Down Expand Up @@ -344,6 +345,15 @@ public function scopeScoreable(Builder $query): void
$query->where('approved', '>', 0);
}

public function scopeToBeRanked(Builder $query, Ruleset $ruleset)
{
return $query->qualified()
->withoutTrashed()
->withModesForRanking($ruleset->value)
->where('queued_at', '<', now()->subDays($GLOBALS['cfg']['osu']['beatmapset']['minimum_days_for_rank']))
->whereDoesntHave('beatmapDiscussions', fn ($q) => $q->openIssues());
}

public function scopeWithModesForRanking($query, $modeInts)
{
if (!is_array($modeInts)) {
Expand Down Expand Up @@ -862,7 +872,10 @@ public function removeFromLoved(User $user, string $reason)

public function rank()
{
if (!$this->isQualified()) {
if (
!$this->isQualified()
|| $this->beatmapDiscussions()->openIssues()->exists()
) {
return false;
}

Expand Down
17 changes: 17 additions & 0 deletions app/Models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@
*/
class Group extends Model implements AfterCommit
{
// Identifier of groups which involved in permission checks
// and assumed to always exist in database.
const PRIV_IDENTIFIERS = [
'admin',
'alumni',
'announce',
'bng',
'bng_limited',
'bot',
'default',
'dev',
'gmt',
'loved',
'nat',
'no_profile',
];

public $timestamps = false;

protected $casts = [
Expand Down
2 changes: 2 additions & 0 deletions app/Models/Multiplayer/ScoreLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public static function complete(ScoreToken $token, array $params): static
$playlistItem = $token->playlistItem;
$requiredMods = array_column($playlistItem->required_mods, 'acronym');
$mods = array_column($score->data->mods, 'acronym');
$mods = app('mods')->excludeModsAlwaysValidForSubmission($playlistItem->ruleset_id, $mods);

if (!empty($requiredMods)) {
if (!empty(array_diff($requiredMods, $mods))) {
throw new InvariantException('This play does not include the mods required.');
Expand Down
9 changes: 7 additions & 2 deletions database/factories/BeatmapDiscussionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ public function general()

public function mapperNote()
{
return $this->state(['message_type' => 'mapper_note']);
return $this->messageType('mapper_note');
}

public function messageType(string $type)
{
return $this->state(['message_type' => $type]);
}

public function problem()
{
return $this->state(['message_type' => 'problem']);
return $this->messageType('problem');
}

public function review()
Expand Down
6 changes: 6 additions & 0 deletions database/factories/BeatmapFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Database\Factories;

use App\Enums\Ruleset;
use App\Models\Beatmap;
use App\Models\Beatmapset;

Expand Down Expand Up @@ -72,6 +73,11 @@ public function ranked(): static
return $this->state(['approved' => Beatmapset::STATES['ranked']]);
}

public function ruleset(Ruleset $ruleset): static
{
return $this->state(['playmode' => $ruleset->value]);
}

public function wip(): static
{
return $this->state(['approved' => Beatmapset::STATES['wip']]);
Expand Down
4 changes: 2 additions & 2 deletions database/factories/BeatmapsetFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ public function pending()
]);
}

public function qualified()
public function qualified(?\DateTimeInterface $approvedAt = null)
{
$approvedAt = now();
$approvedAt ??= now();

return $this->state([
'approved' => Beatmapset::STATES['qualified'],
Expand Down
Loading

0 comments on commit 7c86d47

Please sign in to comment.