Skip to content

Commit

Permalink
Finish 5.4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Schlaefer committed Oct 20, 2019
2 parents 2252220 + e345bb6 commit 7d085ea
Show file tree
Hide file tree
Showing 18 changed files with 494 additions and 216 deletions.
12 changes: 11 additions & 1 deletion config/app.php
Expand Up @@ -163,7 +163,17 @@
'path' => CACHE,
'groups' => ['short'],
'duration' => 180
]
],
/**
* Long term forever cache
*/
'long' => [
'className' => $cache['engine'],
'prefix' => $cache['prefix'],
'path' => CACHE,
'groups' => ['long'],
'duration' => 31536000
],
],

/**
Expand Down
10 changes: 7 additions & 3 deletions config/saito_config.php
Expand Up @@ -86,10 +86,14 @@
],
'debug' => [
/**
* logs emails in debug.log instead of sending them
* Log emails in debug.log instead of sending them.
*/
'email' => false
]
'email' => false,
/**
* Log additional non-error information in info.log
*/
'logInfo' => false,
],
]
];

Expand Down
118 changes: 36 additions & 82 deletions plugins/Cron/src/Lib/Cron.php
Expand Up @@ -13,50 +13,39 @@
namespace Cron\Lib;

use Cake\Cache\Cache;
use Cake\Core\Configure;
use Cake\Log\Log;

class Cron
{
/** @var array */
protected $jobs = [];

protected $_jobs = [];
/** @var int */
protected $now;

protected $_lastRuns = null;

protected $_now;

/**
* defines time intervals in seconds
*
* @var array
*/
protected $_dues = [
// a little shorter than a Cake's default cache-config invalidation hour
'hourly' => 3300,
// if no cron job was triggered in one hour then Cake's default cache file is
// invalidated and hourly is also triggered
'daily' => 86400
];
/** @var array|null Null if not intialized */
private $lastRuns = null;

/**
* Constructor
*/
public function __construct()
{
$this->_now = time();
$this->now = time();
}

/**
* Add cron job
*
* @param string $id unique ID for cron job
* @param mixed $due due intervall
* @param string $due due intervall
* @param callable $func cron job
* @return void
* @return self
*/
public function addCronJob($id, $due, callable $func)
public function addCronJob(string $id, string $due, callable $func): self
{
$this->_jobs[$id] = new CronJob($id, $due, $func);
$this->jobs[$id] = new CronJob($id, $due, $func);

return $this;
}

/**
Expand All @@ -66,31 +55,23 @@ public function addCronJob($id, $due, callable $func)
*/
public function execute()
{
$lastRuns = $this->_getLastRuns();
$jobsExecuted = 0;
foreach ($this->_jobs as $job) {
if (!empty($lastRuns[$job->due][$job->uid])) {
if (isset($this->_dues[$job->due])) {
$due = $lastRuns[$job->due][$job->uid] + $this->_dues[$job->due];
} else {
$due = strtotime(
$job->due,
$lastRuns[$job->due][$job->uid]
);
}
if ($this->_now < $due) {
$this->lastRuns = $this->getLastRuns();
$jobsExecuted = false;
foreach ($this->jobs as $job) {
$uid = $job->getUid();
$due = $job->getDue();
if (!empty($this->lastRuns[$uid])) {
if ($this->now < $this->lastRuns[$uid]) {
continue;
}
}
$jobsExecuted++;
$job->execute();
$this->_log('Run cron-job ' . $job->uid);
$this->_lastRuns[$job->due][$job->uid] = $this->_now;
$jobsExecuted = true;
$this->lastRuns[$uid] = $due;
}
if ($jobsExecuted === 0) {
return;
if ($jobsExecuted) {
$this->saveLastRuns();
}
Cache::write('Plugin.Cron.lastRuns', $this->_lastRuns);
}

/**
Expand All @@ -100,59 +81,32 @@ public function execute()
*/
public function clearHistory()
{
$this->_lastRuns = [];
$this->_now = time();
Cache::write('Plugin.Cron.lastRuns', $this->_getNewCacheData());
}

/**
* Create new cache data
*
* @return array
*/
protected function _getNewCacheData()
{
return ['meta' => ['lastDailyReset' => $this->_now]];
$this->now = time();
$this->lastRuns = [];
$this->saveLastRuns();
}

/**
* Get last cron runs
*
* @return array|mixed|null
* @return array
*/
protected function _getLastRuns()
protected function getLastRuns(): array
{
if ($this->_lastRuns) {
return $this->_lastRuns;
if ($this->lastRuns === null) {
$this->lastRuns = Cache::read('Plugin.Cron.lastRuns', 'long') ?: [];
}
$cache = Cache::read('Plugin.Cron.lastRuns');

if (!isset($cache['meta']['lastDailyReset']) || // cache file is not created yet
$cache['meta']['lastDailyReset'] + $this->_dues['daily'] < $this->_now // cache is outdated
) {
$cache = $this->_getNewCacheData();
// This request may trigger many jobs and take some time.
// Update cache immediately and not after all jobs are done (at the
// end of this request), so that following requests arriving in that
// time-frame don't assume they have to run the same jobs too.
Cache::write('Plugin.Cron.lastRuns', $cache);
}
$this->_lastRuns = $cache;

return $this->_lastRuns;
return $this->lastRuns;
}

/**
* Logger
*
* @param string $msg message
* Create new cache data
*
* @return bool
* @return void
*/
protected function _log($msg)
protected function saveLastRuns(): void
{
if (Configure::read('Saito.Globals.logInfo')) {
return Log::write('info', $msg, ['scope' => ['saito.info']]);
}
Cache::write('Plugin.Cron.lastRuns', $this->lastRuns, 'long');
}
}
57 changes: 47 additions & 10 deletions plugins/Cron/src/Lib/CronJob.php
Expand Up @@ -12,45 +12,82 @@

namespace Cron\Lib;

use Cake\Core\Configure;
use Cake\Log\Log;
use Stopwatch\Lib\Stopwatch;

class CronJob
{

/**
* @var string
*/
public $uid;
private $uid;

/**
* @var mixed
* @var int
*/
public $due;
private $due;

/**
* @var callable
*/
protected $_garbage;
private $func;

/**
* Constructor
*
* @param string $uid unique ID for cron job
* @param mixed $due due intervall
* @param string $due due intervall
* @param callable $func cron job
*/
public function __construct($uid, $due, callable $func)
public function __construct(string $uid, string $due, callable $func)
{
$this->uid = $uid;
$this->due = $due;
$this->_garbage = $func;
$this->due = strtotime($due);
if (!$this->due) {
throw new \InvalidArgumentException(
sprintf('Cannot convert "%s" into a timestamp.', $due),
1571567221
);
}
$this->func = $func;
}

/**
* Get the value of uid
*
* @return string
*/
public function getUid(): string
{
return $this->uid;
}

/**
* When should the job be next executed
*
* @return int UNIX-timestamp
*/
public function getDue(): int
{
return $this->due;
}

/**
* Execute cron job
*
* @return void
*/
public function execute()
public function execute(): void
{
call_user_func($this->_garbage);
$msg = 'Cron.CronJob::execute ' . $this->getUid();
if (Configure::read('Saito.debug.logInfo')) {
Log::write('info', $msg, ['scope' => ['saito.info']]);
}

Stopwatch::start($msg);
call_user_func($this->func);
Stopwatch::stop($msg);
}
}
43 changes: 43 additions & 0 deletions plugins/Cron/tests/TestCase/Lib/CronJobTest.php
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

/**
* Saito - The Threaded Web Forum
*
* @copyright Copyright (c) the Saito Project Developers
* @link https://github.com/Schlaefer/Saito
* @license http://opensource.org/licenses/MIT
*/

namespace Cron\Test;

use Cake\TestSuite\TestCase;
use Cron\Lib\CronJob;

class CronJobTest extends TestCase
{
public function testAll()
{
$mock = $this->getMockBuilder('stdClass')
->setMethods(['callback'])
->getMock();
$mock->expects($this->once())->method('callback');

$due = '+1 day';
$job = new CronJob('foo', $due, [$mock, 'callback']);

$this->assertEquals('foo', $job->getUid());
$due = new \DateTimeImmutable($due);
$this->assertWithinRange($due->getTimestamp(), $job->getDue(), 2);

$job->execute();
}

public function testConstructorWrongDue()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1571567221);
new CronJob('foo', 'bar', [$this, '__construct']);
}
}

0 comments on commit 7d085ea

Please sign in to comment.