Skip to content

Commit

Permalink
Granular esi scheduling for characters (#684)
Browse files Browse the repository at this point in the history
* squads: trigger filter check using events

* squads: move to one common definition for rules

This also allows plugins to register their own rules!

* add scheduling rule migration

* add scheduling rule migration

* add scheduling rule user interface prototype

This commit also contains a lot of unrelated changes in the view since I had to refactor some things for the layout, which introduces identation changes for almost everything

* Finish character scheduling rule interface

* character filters: trigger data update event with character instead of user

* schedule: apply character scheduling rules to tokens

* styleci

* make some tests pass

* fix setting a scheduling rule for new tokens
  • Loading branch information
recursivetree authored Sep 5, 2024
1 parent b13d283 commit 57f17c2
Show file tree
Hide file tree
Showing 25 changed files with 781 additions and 220 deletions.
33 changes: 33 additions & 0 deletions src/Config/web.characterfilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of SeAT
*
* Copyright (C) 2015 to present Leon Jacobs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

return [
['name' => 'scopes', 'src' => 'seatcore::fastlookup.scopes', 'path' => 'refresh_token', 'field' => 'scopes', 'label' => 'Scopes'],
['name' => 'character', 'src' => 'seatcore::fastlookup.characters', 'path' => '', 'field' => 'character_id', 'label' => 'Character'],
['name' => 'title', 'src' => 'seatcore::fastlookup.titles', 'path' => 'titles', 'field' => 'id', 'label' => 'Title'],
['name' => 'corporation', 'src' => 'seatcore::fastlookup.corporations', 'path' => 'affiliation', 'field' => 'corporation_id', 'label' => 'Corporation'],
['name' => 'alliance', 'src' => 'seatcore::fastlookup.alliances', 'path' => 'affiliation', 'field' => 'alliance_id', 'label' => 'Alliance'],
['name' => 'skill', 'src' => 'seatcore::fastlookup.skills', 'path' => 'skills', 'field' => 'skill_id', 'label' => 'Skill'],
['name' => 'skill_level', 'src' => [['id' => 1, 'text' => 'Level 1'], ['id' => 2, 'text' => 'Level 2'], ['id' => 3, 'text' => 'Level 3'], ['id' => 4, 'text' => 'Level 4'], ['id' => 5, 'text' => 'Level 5']], 'path' => 'skills', 'field' => 'trained_skill_level', 'label' => 'Skill Level'],
['name' => 'type', 'src' => 'seatcore::fastlookup.items', 'path' => 'assets', 'field' => 'type_id', 'label' => 'Item'],
['name' => 'role', 'src' => 'seatcore::fastlookup.roles', 'path' => 'corporation_roles', 'field' => 'role', 'label' => 'Role'],
];
45 changes: 45 additions & 0 deletions src/Events/CharacterFilterDataUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of SeAT
*
* Copyright (C) 2015 to present Leon Jacobs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

namespace Seat\Web\Events;

use Illuminate\Queue\SerializesModels;
use Seat\Eveapi\Models\Character\CharacterInfo;
use Seat\Web\Models\User;

/**
* This event is fired when character filters, like used in squads, need to recompute because the data they are based on changed.
*/
class CharacterFilterDataUpdate
{
use SerializesModels;

public CharacterInfo $character;

/**
* @param User $user
*/
public function __construct(CharacterInfo $character)
{
$this->character = $character;
}
}
45 changes: 45 additions & 0 deletions src/Http/Composers/CharacterFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of SeAT
*
* Copyright (C) 2015 to present Leon Jacobs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

namespace Seat\Web\Http\Composers;

use Illuminate\View\View;

class CharacterFilter
{
public function compose(View $view)
{
$rules = config('web.characterfilter');

// work with raw arrays since the filter code requires an array of objects, and laravel collections don't like to give us that
$newrules = [];
foreach ($rules as $rule) {
// convert route names to urls, but keep arrays with hardcoded options
if(is_string($rule['src'])){
$rule['src'] = route($rule['src']);
}
$newrules[] = (object) $rule;
}

$view->with('characterFilterRules', $newrules);
}
}
59 changes: 57 additions & 2 deletions src/Http/Controllers/Configuration/ScheduleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
namespace Seat\Web\Http\Controllers\Configuration;

use Artisan;
use Illuminate\Http\Request;
use Seat\Eveapi\Models\RefreshToken;
use Seat\Services\Models\Schedule;
use Seat\Web\Http\Controllers\Controller;
use Seat\Web\Http\Validation\NewSchedule;
use Seat\Web\Models\CharacterSchedulingRule;

/**
* Class ScheduleController.
Expand All @@ -52,11 +55,12 @@ public function listSchedule()
'every five minutes' => '*/5 * * * *',
'every ten minutes' => '*/10 * * * *',
'every thirty minutes' => '*/30 * * * *',

];

$scheduling_rules = CharacterSchedulingRule::all();

return view('web::configuration.schedule.view',
compact('schedule', 'commands', 'expressions'));
compact('schedule', 'commands', 'expressions', 'scheduling_rules'));
}

/**
Expand Down Expand Up @@ -85,4 +89,55 @@ public function deleteSchedule(int $schedule_id)
return redirect()->back()
->with('success', 'Schedule entry deleted!');
}

public function createSchedulingRule(Request $request)
{
$request->validate([
'filters' => 'required|json',
'name' => 'required|string',
'time' => 'required|numeric',
'timeunit' => 'required|in:hour,day,week',
]);

// $time_modifier: conversion factor from timeunit to seconds
if($request->timeunit === 'hour') {
$time_modifier = 60 * 60;
} elseif ($request->timeunit === 'day') {
$time_modifier = 60 * 60 * 24;
} elseif ($request->timeunit === 'week') {
$time_modifier = 60 * 60 * 24 * 7;
}
$time = $request->time * $time_modifier;

$rule = CharacterSchedulingRule::where('name', $request->name)->first();
if($rule === null) {
$rule = new CharacterSchedulingRule();
$rule->name = $request->name;
}
$rule->interval = $time;
$rule->filter = $request->filters;
$rule->save();

RefreshToken::all()->each(function ($token) {
CharacterSchedulingRule::updateRefreshTokenSchedule($token);
});

return redirect()->back()
->with('success', 'Character Scheduling Rule added!');
}

public function deleteSchedulingRule(Request $request)
{
$request->validate([
'rule_id' => 'required|numeric',
]);

CharacterSchedulingRule::destroy($request->rule_id);

RefreshToken::all()->each(function ($token) {
CharacterSchedulingRule::updateRefreshTokenSchedule($token);
});

return redirect()->back()->with('success', 'Successfully removed character scheduling rule!');
}
}
8 changes: 8 additions & 0 deletions src/Http/Routes/Configuration/Schedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@
Route::get('/delete/{schedule_id}')
->name('seatcore::configuration.schedule.delete')
->uses('ScheduleController@deleteSchedule');

Route::post('/rules/create')
->name('seatcore::configuration.schedule.rule.create')
->uses('ScheduleController@createSchedulingRule');

Route::post('/rules/delete')
->name('seatcore::configuration.schedule.rule.delete')
->uses('ScheduleController@deleteSchedulingRule');
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

namespace Seat\Web\Observers;
namespace Seat\Web\Listeners;

use Illuminate\Database\Eloquent\Model;
use Seat\Web\Exceptions\InvalidFilterException;
use Seat\Web\Events\CharacterFilterDataUpdate;
use Seat\Web\Models\Squads\Squad;
use Seat\Web\Models\User;

/**
* Class AbstractSquadObserver.
*
* @package Seat\Web\Observers
*/
abstract class AbstractSquadObserver
class CharacterFilterDataUpdatedSquads
{
/**
* Return the User owning the model which fired the catch event.
*
* @param \Illuminate\Database\Eloquent\Model $fired_model The model which fired the catch event
* @return \Seat\Web\Models\User|null The user owning this model
*/
abstract protected function findRelatedUser(Model $fired_model): ?User;

/**
* Update squads to which the user owning model firing the event is member.
*
* @param \Illuminate\Database\Eloquent\Model $fired_model The model which fired the catch event
*
* @throws InvalidFilterException
*/
protected function updateUserSquads(Model $fired_model)
public static function handle(CharacterFilterDataUpdate $event)
{
$user = $this->findRelatedUser($fired_model);

if (! $user)
return;
$user = $event->character->user;

$member_squads = $user->squads;

Expand Down
34 changes: 34 additions & 0 deletions src/Listeners/CharacterFilterDataUpdatedTokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of SeAT
*
* Copyright (C) 2015 to present Leon Jacobs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

namespace Seat\Web\Listeners;

use Seat\Web\Events\CharacterFilterDataUpdate;
use Seat\Web\Models\CharacterSchedulingRule;

class CharacterFilterDataUpdatedTokens
{
public static function handle(CharacterFilterDataUpdate $update)
{
CharacterSchedulingRule::updateRefreshTokenSchedule($update->character->refresh_token);
}
}
93 changes: 93 additions & 0 deletions src/Models/CharacterSchedulingRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/*
* This file is part of SeAT
*
* Copyright (C) 2015 to present Leon Jacobs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

namespace Seat\Web\Models;

use Seat\Eveapi\Models\Character\CharacterInfo;
use Seat\Eveapi\Models\RefreshToken;
use Seat\Eveapi\Models\RefreshTokenSchedule;
use Seat\Services\Models\ExtensibleModel;
use stdClass;

/**
* @property int $id
* @property string name
* @property string filter
* @property int interval
*/
class CharacterSchedulingRule extends ExtensibleModel
{
use Filterable;

/**
* @var bool
*/
public $timestamps = false;

/**
* The filters to use.
*
* @return \stdClass
*/
public function getFilters(): stdClass
{
return json_decode($this->filter);
}

/**
* Recomputes the update interval of a character and saves it in the refresh_token_schedules table.
*
* @param RefreshToken $token
* @return void
*/
public static function updateRefreshTokenSchedule(RefreshToken $token): void
{
$schedule = $token->token_schedule;

if($schedule === null) {
$schedule = new RefreshTokenSchedule();
$schedule->character_id = $token->character_id;
}

$schedule->update_interval = self::getCharacterSchedulingInterval($token->character);
$schedule->save();
}

/**
* Computes the scheduling interval from the character scheduling rules for a character.
*
* @param CharacterInfo $character
* @return int
*/
private static function getCharacterSchedulingInterval(CharacterInfo $character): int
{
$scheduling_rules = CharacterSchedulingRule::orderBy('interval', 'asc')->get();

foreach ($scheduling_rules as $rule) {
if($rule->isEligible($character)) {
return $rule->interval;
}
}

return 60 * 60; // 1 hour
}
}
Loading

0 comments on commit 57f17c2

Please sign in to comment.