Commit 7948341a authored by Robin Appelman's avatar Robin Appelman
Browse files

Rework background job system

parent d0a5fe1f
......@@ -49,7 +49,7 @@ $entry = array(
'name' => 'LDAP'
);
OCP\Backgroundjob::addRegularTask('OCA\user_ldap\lib\Jobs', 'updateGroups');
OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs');
if(OCP\App::isEnabled('user_webdavauth')) {
OCP\Util::writeLog('user_ldap',
'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour',
......
......@@ -23,20 +23,22 @@
namespace OCA\user_ldap\lib;
class Jobs {
class Jobs extends \OC\BackgroundJob\TimedJob {
static private $groupsFromDB;
static private $groupBE;
static private $connector;
public function __construct(){
$this->interval = self::getRefreshInterval();
}
public function run($argument){
Jobs::updateGroups();
}
static public function updateGroups() {
\OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', \OCP\Util::DEBUG);
$lastUpdate = \OCP\Config::getAppValue('user_ldap', 'bgjUpdateGroupsLastRun', 0);
if((time() - $lastUpdate) < self::getRefreshInterval()) {
\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – last run too fresh, aborting.', \OCP\Util::DEBUG);
//komm runter Werner die Maurer geben ein aus
return;
}
$knownGroups = array_keys(self::getKnownGroups());
$actualGroups = self::getGroupBE()->getGroups();
......@@ -45,7 +47,6 @@ class Jobs {
\OCP\Util::writeLog('user_ldap',
'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
\OCP\Util::INFO);
\OCP\Config::setAppValue('user_ldap', 'bgjUpdateGroupsLastRun', time());
return;
}
......@@ -53,8 +54,6 @@ class Jobs {
self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
\OCP\Config::setAppValue('user_ldap', 'bgjUpdateGroupsLastRun', time());
\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', \OCP\Util::DEBUG);
}
......
......@@ -94,7 +94,11 @@ if (OC::$CLI) {
touch(TemporaryCronClass::$lockfile);
// Work
OC_BackgroundJob_Worker::doAllSteps();
$jobList = new \OC\BackgroundJob\JobList();
$jobs = $jobList->getAll();
foreach ($jobs as $job) {
$job->execute($jobList);
}
} else {
// We call cron.php from some website
if ($appmode == 'cron') {
......@@ -102,7 +106,10 @@ if (OC::$CLI) {
OC_JSON::error(array('data' => array('message' => 'Backgroundjobs are using system cron!')));
} else {
// Work and success :-)
OC_BackgroundJob_Worker::doNextStep();
$jobList = new \OC\BackgroundJob\JobList();
$job = $jobList->getNext();
$job->execute($jobList);
$jobList->setLastJob($job);
OC_JSON::success();
}
}
......
<?php
/**
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\BackgroundJob;
abstract class Job {
protected $id;
protected $lastRun;
protected $argument;
/**
* @param JobList $jobList
*/
public function execute($jobList) {
$jobList->setLastRun($this);
$this->run($this->argument);
}
abstract protected function run($argument);
public function setId($id) {
$this->id = $id;
}
public function setLastRun($lastRun) {
$this->lastRun = $lastRun;
}
public function setArgument($argument) {
$this->argument = $argument;
}
public function getId() {
return $this->id;
}
public function getLastRun() {
return $this->lastRun();
}
}
<?php
/**
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\BackgroundJob;
/**
* Class QueuedJob
*
* create a background job that is to be executed once
*
* @package OC\BackgroundJob
*/
class JobList {
/**
* @param Job|string $job
* @param mixed $argument
*/
public function add($job, $argument = null) {
if (!$this->has($job, $argument)) {
if ($job instanceof Job) {
$class = get_class($job);
} else {
$class = $job;
}
$argument = json_encode($argument);
$query = \OC_DB::prepare('INSERT INTO `*PREFIX*jobs`(`class`, `argument`, `last_run`) VALUES(?, ?, 0)');
$query->execute(array($class, $argument));
}
}
/**
* @param Job|string $job
* @param mixed $argument
*/
public function remove($job, $argument = null) {
if ($job instanceof Job) {
$class = get_class($job);
} else {
$class = $job;
}
if (!is_null($argument)) {
$argument = json_encode($argument);
$query = \OC_DB::prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?');
$query->execute(array($class, $argument));
} else {
$query = \OC_DB::prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ?');
$query->execute(array($class));
}
}
/**
* check if a job is in the list
*
* @param $job
* @param mixed $argument
* @return bool
*/
public function has($job, $argument) {
if ($job instanceof Job) {
$class = get_class($job);
} else {
$class = $job;
}
$argument = json_encode($argument);
$query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?');
$result = $query->execute(array($class, $argument));
return (bool)$result->fetchRow();
}
/**
* get all jobs in the list
*
* @return Job[]
*/
public function getAll() {
$query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs`');
$result = $query->execute();
$jobs = array();
while ($row = $result->fetchRow()) {
$jobs[] = $this->buildJob($row);
}
return $jobs;
}
/**
* get the next job in the list
*
* @return Job
*/
public function getNext() {
$lastId = $this->getLastJob();
$query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` > ? ORDER BY `id` ASC', 1);
$result = $query->execute(array($lastId));
if ($row = $result->fetchRow()) {
return $this->buildJob($row);
} else {
//begin at the start of the queue
$query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` ORDER BY `id` ASC', 1);
$result = $query->execute();
if ($row = $result->fetchRow()) {
return $this->buildJob($row);
} else {
return null; //empty job list
}
}
}
/**
* get the job object from a row in the db
*
* @param array $row
* @return Job
*/
private function buildJob($row) {
$class = $row['class'];
/**
* @var Job $job
*/
$job = new $class();
$job->setId($row['id']);
$job->setLastRun($row['last_run']);
$job->setArgument(json_decode($row['argument']));
return $job;
}
/**
* set the job that was last ran
*
* @param Job $job
*/
public function setLastJob($job) {
\OC_Appconfig::setValue('backgroundjob', 'lastjob', $job->getId());
}
/**
* get the id of the last ran job
*
* @return int
*/
public function getLastJob() {
return \OC_Appconfig::getValue('backgroundjob', 'lastjob', 0);
}
/**
* set the lastRun of $job to now
*
* @param Job $job
*/
public function setLastRun($job) {
$query = \OC_DB::prepare('UPDATE `*PREFIX*jobs` SET `last_run` = ? WHERE `id` = ?');
$query->execute(array(time(), $job->getId()));
}
}
<?php
/**
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\BackgroundJob;
/**
* Class QueuedJob
*
* create a background job that is to be executed once
*
* @package OC\BackgroundJob
*/
abstract class QueuedJob extends Job {
/**
* run the job, then remove it from the joblist
*
* @param JobList $jobList
*/
public function execute($jobList) {
$jobList->remove($this);
$this->run($this->argument);
}
}
<?php
/**
* ownCloud
*
* @author Jakob Sack
* @copyright 2012 Jakob Sack owncloud@jakobsack.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This class manages our queued tasks.
*/
class OC_BackgroundJob_QueuedTask{
/**
* @brief Gets one queued task
* @param $id ID of the task
* @return associative array
*/
public static function find( $id ) {
$stmt = OC_DB::prepare( 'SELECT * FROM `*PREFIX*queuedtasks` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
return $result->fetchRow();
}
/**
* @brief Gets all queued tasks
* @return array with associative arrays
*/
public static function all() {
// Array for objects
$return = array();
// Get Data
$stmt = OC_DB::prepare( 'SELECT * FROM `*PREFIX*queuedtasks`' );
$result = $stmt->execute(array());
while( $row = $result->fetchRow()) {
$return[] = $row;
}
return $return;
}
/**
* @brief Gets all queued tasks of a specific app
* @param $app app name
* @return array with associative arrays
*/
public static function whereAppIs( $app ) {
// Array for objects
$return = array();
// Get Data
$stmt = OC_DB::prepare( 'SELECT * FROM `*PREFIX*queuedtasks` WHERE `app` = ?' );
$result = $stmt->execute(array($app));
while( $row = $result->fetchRow()) {
$return[] = $row;
}
// Und weg damit
return $return;
}
/**
* @brief queues a task
* @param $app app name
* @param $klass class name
* @param $method method name
* @param $parameters all useful data as text
* @return id of task
*/
public static function add( $app, $klass, $method, $parameters ) {
$stmt = OC_DB::prepare( 'INSERT INTO `*PREFIX*queuedtasks` (`app`, `klass`, `method`, `parameters`)'
.' VALUES(?,?,?,?)' );
$result = $stmt->execute(array($app, $klass, $method, $parameters ));
return OC_DB::insertid();
}
/**
* @brief deletes a queued task
* @param $id id of task
* @return true/false
*
* Deletes a report
*/
public static function delete( $id ) {
$stmt = OC_DB::prepare( 'DELETE FROM `*PREFIX*queuedtasks` WHERE `id` = ?' );
$result = $stmt->execute(array($id));
return true;
}
}
<?php
/**
* ownCloud
*
* @author Jakob Sack
* @copyright 2012 Jakob Sack owncloud@jakobsack.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This class manages the regular tasks.
*/
class OC_BackgroundJob_RegularTask{
static private $registered = array();
/**
* @brief creates a regular task
* @param $klass class name
* @param $method method name
* @return true
*/
static public function register( $klass, $method ) {
// Create the data structure
self::$registered["$klass-$method"] = array( $klass, $method );
// No chance for failure ;-)
return true;
}
/**
* @brief gets all regular tasks
* @return associative array
*
* key is string "$klass-$method", value is array( $klass, $method )
*/
static public function all() {
return self::$registered;
}
}
<?php
/**
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OC\BackgroundJob;
/**
* Class QueuedJob
*
* create a background job that is to be executed at an interval
*
* @package OC\BackgroundJob
*/
abstract class TimedJob extends Job {
protected $interval = 0;
/**
* set the interval for the job
*
* @param int $interval
*/
public function setInterval($interval) {
$this->interval = $interval;
}
/**
* run the job if
*
* @param JobList $jobList
*/
public function execute($jobList) {
if ((time() - $this->lastRun) > $this->interval) {
$jobList->setLastRun($this);
$this->run($this->argument);
}
}
}
<?php
/**
* ownCloud
*
* @author Jakob Sack
* @copyright 2012 Jakob Sack owncloud@jakobsack.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* This class does the dirty work.
*
* TODO: locking in doAllSteps
*/
class OC_BackgroundJob_Worker{
/**
* @brief executes all tasks
* @return boolean
*
* This method executes all regular tasks and then all queued tasks.
* This method should be called by cli scripts that do not let the user
* wait.
*/
public static function doAllSteps() {
// Do our regular work
$lasttask = OC_Appconfig::getValue( 'core', 'backgroundjobs_task', '' );
$regular_tasks = OC_BackgroundJob_RegularTask::all();
ksort( $regular_tasks );
foreach( $regular_tasks as $key => $value ) {
if( strcmp( $key, $lasttask ) > 0 ) {
// Set "restart here" config value
OC_Appconfig::setValue( 'core', 'backgroundjobs_task', $key );
call_user_func( $value );
}
}
// Reset "start here" config value
OC_Appconfig::setValue( 'core', 'backgroundjobs_task', '' );
// Do our queued tasks
$queued_tasks = OC_BackgroundJob_QueuedTask::all();
foreach( $queued_tasks as $task ) {
OC_BackgroundJob_QueuedTask::delete( $task['id'] );
call_user_func( array( $task['klass'], $task['method'] ), $task['parameters'] );
}
return true;
}
/**
* @brief does a single task
* @return boolean
*
* This method executes one task. It saves the last state and continues
* with the next step. This method should be used by webcron and ajax
* services.
*/
public static function doNextStep() {
$laststep = OC_Appconfig::getValue( 'core', 'backgroundjobs_step', 'regular_tasks' );
if( $laststep == 'regular_tasks' ) {
// get last app
$lasttask = OC_Appconfig::getValue( 'core', 'backgroundjobs_task', '' );
// What's the next step?
$regular_tasks = OC_BackgroundJob_RegularTask::all();
ksort( $regular_tasks );
$done = false;
// search for next background job
foreach( $regular_tasks as $key => $value ) {
if( strcmp( $key, $lasttask ) > 0 ) {
OC_Appconfig::setValue( 'core', 'backgroundjobs_task', $key );
$done = true;
call_user_func( $value );
break;
}
}
if( $done == false ) {
// Next time load queued tasks
OC_Appconfig::setValue( 'core', 'backgroundjobs_step', 'queued_tasks' );
}
}
else{
$tasks = OC_BackgroundJob_QueuedTask::all();
if( count( $tasks )) {
$task = $tasks[0];
// delete job before we execute it. This prevents endless loops
// of failing jobs.
OC_BackgroundJob_QueuedTask::delete($task['id']);
// execute job
call_user_func( array( $task['klass'], $task['method'] ), $task['parameters'] );
}