diff --git a/cron.php b/cron.php index 0d2c07b2d9..44ca421328 100644 --- a/cron.php +++ b/cron.php @@ -97,7 +97,7 @@ try { touch(TemporaryCronClass::$lockfile); // Work - $jobList = new \OC\BackgroundJob\JobList(); + $jobList = \OC::$server->getJobList(); $jobs = $jobList->getAll(); foreach ($jobs as $job) { $job->execute($jobList, $logger); @@ -109,7 +109,7 @@ try { OC_JSON::error(array('data' => array('message' => 'Backgroundjobs are using system cron!'))); } else { // Work and success :-) - $jobList = new \OC\BackgroundJob\JobList(); + $jobList = \OC::$server->getJobList(); $job = $jobList->getNext(); $job->execute($jobList, $logger); $jobList->setLastJob($job); diff --git a/lib/private/backgroundjob/job.php b/lib/private/backgroundjob/job.php index 92bd0f8fdb..0cef401bc2 100644 --- a/lib/private/backgroundjob/job.php +++ b/lib/private/backgroundjob/job.php @@ -8,7 +8,9 @@ namespace OC\BackgroundJob; -abstract class Job { +use OCP\BackgroundJob\IJob; + +abstract class Job implements IJob { /** * @var int $id */ diff --git a/lib/private/backgroundjob/joblist.php b/lib/private/backgroundjob/joblist.php index 99743a70c7..586e99a3cb 100644 --- a/lib/private/backgroundjob/joblist.php +++ b/lib/private/backgroundjob/joblist.php @@ -1,6 +1,6 @@ + * Copyright (c) 2014 Robin Appelman * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. @@ -8,14 +8,28 @@ namespace OC\BackgroundJob; -/** - * Class QueuedJob - * - * create a background job that is to be executed once - * - * @package OC\BackgroundJob - */ -class JobList { +use OCP\BackgroundJob\IJobList; + +class JobList implements IJobList { + /** + * @var \OCP\IDBConnection + */ + private $conn; + + /** + * @var \OCP\IConfig $config + */ + private $config; + + /** + * @param \OCP\IDBConnection $conn + * @param \OCP\IConfig $config + */ + public function __construct($conn, $config) { + $this->conn = $conn; + $this->config = $config; + } + /** * @param Job|string $job * @param mixed $argument @@ -28,7 +42,7 @@ class JobList { $class = $job; } $argument = json_encode($argument); - $query = \OC_DB::prepare('INSERT INTO `*PREFIX*jobs`(`class`, `argument`, `last_run`) VALUES(?, ?, 0)'); + $query = $this->conn->prepare('INSERT INTO `*PREFIX*jobs`(`class`, `argument`, `last_run`) VALUES(?, ?, 0)'); $query->execute(array($class, $argument)); } } @@ -45,10 +59,10 @@ class JobList { } if (!is_null($argument)) { $argument = json_encode($argument); - $query = \OC_DB::prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?'); + $query = $this->conn->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 = $this->conn->prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ?'); $query->execute(array($class)); } } @@ -67,9 +81,9 @@ class JobList { $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(); + $query = $this->conn->prepare('SELECT `id` FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?'); + $query->execute(array($class, $argument)); + return (bool)$query->fetch(); } /** @@ -78,10 +92,10 @@ class JobList { * @return Job[] */ public function getAll() { - $query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs`'); - $result = $query->execute(); + $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs`'); + $query->execute(); $jobs = array(); - while ($row = $result->fetchRow()) { + while ($row = $query->fetch()) { $jobs[] = $this->buildJob($row); } return $jobs; @@ -94,15 +108,15 @@ class JobList { */ 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()) { + $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` > ? ORDER BY `id` ASC', 1); + $query->execute(array($lastId)); + if ($row = $query->fetch()) { 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()) { + $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` ORDER BY `id` ASC', 1); + $query->execute(); + if ($row = $query->fetch()) { return $this->buildJob($row); } else { return null; //empty job list @@ -115,9 +129,9 @@ class JobList { * @return Job */ public function getById($id) { - $query = \OC_DB::prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` = ?'); - $result = $query->execute(array($id)); - if ($row = $result->fetchRow()) { + $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` = ?'); + $query->execute(array($id)); + if ($row = $query->fetch()) { return $this->buildJob($row); } else { return null; @@ -148,7 +162,7 @@ class JobList { * @param Job $job */ public function setLastJob($job) { - \OC_Appconfig::setValue('backgroundjob', 'lastjob', $job->getId()); + $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId()); } /** @@ -157,7 +171,7 @@ class JobList { * @return int */ public function getLastJob() { - return \OC_Appconfig::getValue('backgroundjob', 'lastjob', 0); + return $this->config->getAppValue('backgroundjob', 'lastjob', 0); } /** @@ -166,7 +180,7 @@ class JobList { * @param Job $job */ public function setLastRun($job) { - $query = \OC_DB::prepare('UPDATE `*PREFIX*jobs` SET `last_run` = ? WHERE `id` = ?'); + $query = $this->conn->prepare('UPDATE `*PREFIX*jobs` SET `last_run` = ? WHERE `id` = ?'); $query->execute(array(time(), $job->getId())); } } diff --git a/lib/private/server.php b/lib/private/server.php index fc8f170dc4..7696fc207f 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -151,6 +151,13 @@ class Server extends SimpleContainer implements IServerContainer { $this->registerService('AvatarManager', function($c) { return new AvatarManager(); }); + $this->registerService('JobList', function ($c) { + /** + * @var Server $c + */ + $config = $c->getConfig(); + return new \OC\BackgroundJob\JobList($c->getDatabaseConnection(), $config); + }); } /** @@ -348,4 +355,13 @@ class Server extends SimpleContainer implements IServerContainer { function getActivityManager() { return $this->query('ActivityManager'); } + + /** + * Returns an job list for controlling background jobs + * + * @return \OCP\BackgroundJob\IJobList + */ + function getJobList(){ + return $this->query('JobList'); + } } diff --git a/lib/public/backgroundjob.php b/lib/public/backgroundjob.php index a7f54491df..bcaf6e3545 100644 --- a/lib/public/backgroundjob.php +++ b/lib/public/backgroundjob.php @@ -33,7 +33,7 @@ use \OC\BackgroundJob\JobList; /** * This class provides functions to register backgroundjobs in ownCloud * - * To create a new backgroundjob create a new class that inharits from either \OC\BackgroundJob\Job, + * To create a new backgroundjob create a new class that inherits from either \OC\BackgroundJob\Job, * \OC\BackgroundJob\QueuedJob or \OC\BackgroundJob\TimedJob and register it using * \OCP\BackgroundJob->registerJob($job, $argument), $argument will be passed to the run() function * of the job when the job is executed. @@ -73,7 +73,7 @@ class BackgroundJob { * @param mixed $argument */ public static function registerJob($job, $argument = null) { - $jobList = new JobList(); + $jobList = \OC::$server->getJobList(); $jobList->add($job, $argument); } @@ -99,7 +99,7 @@ class BackgroundJob { * key is string "$klass-$method", value is array( $klass, $method ) */ static public function allRegularTasks() { - $jobList = new JobList(); + $jobList = \OC::$server->getJobList(); $allJobs = $jobList->getAll(); $regularJobs = array(); foreach ($allJobs as $job) { @@ -118,7 +118,7 @@ class BackgroundJob { * @return associative array */ public static function findQueuedTask($id) { - $jobList = new JobList(); + $jobList = \OC::$server->getJobList(); return $jobList->getById($id); } @@ -128,7 +128,7 @@ class BackgroundJob { * @return array with associative arrays */ public static function allQueuedTasks() { - $jobList = new JobList(); + $jobList = \OC::$server->getJobList(); $allJobs = $jobList->getAll(); $queuedJobs = array(); foreach ($allJobs as $job) { @@ -148,7 +148,7 @@ class BackgroundJob { * @return array with associative arrays */ public static function queuedTaskWhereAppIs($app) { - $jobList = new JobList(); + $jobList = \OC::$server->getJobList(); $allJobs = $jobList->getAll(); $queuedJobs = array(); foreach ($allJobs as $job) { @@ -186,7 +186,7 @@ class BackgroundJob { * Deletes a report */ public static function deleteQueuedTask($id) { - $jobList = new JobList(); + $jobList = \OC::$server->getJobList(); $job = $jobList->getById($id); if ($job) { $jobList->remove($job); diff --git a/lib/public/backgroundjob/ijob.php b/lib/public/backgroundjob/ijob.php new file mode 100644 index 0000000000..5231e9537a --- /dev/null +++ b/lib/public/backgroundjob/ijob.php @@ -0,0 +1,42 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCP\BackgroundJob; + +interface IJob { + /** + * Run the background job with the registered argument + * + * @param \OCP\BackgroundJob\IJobList $jobList The job list that manages the state of this job + * @param \OC\Log $logger + */ + public function execute($jobList, $logger = null); + + /** + * Get the id of the background job + * This id is determined by the job list when a job is added to the list + * + * @return int + */ + public function getId(); + + /** + * Get the last time this job was run as unix timestamp + * + * @return int + */ + public function getLastRun(); + + /** + * Get the argument associated with the background job + * This is the argument that will be passed to the background job + * + * @return mixed + */ + public function getArgument(); +} diff --git a/lib/public/backgroundjob/ijoblist.php b/lib/public/backgroundjob/ijoblist.php new file mode 100644 index 0000000000..72f3fe863d --- /dev/null +++ b/lib/public/backgroundjob/ijoblist.php @@ -0,0 +1,77 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCP\BackgroundJob; + +interface IJobList { + /** + * Add a job to the list + * + * @param \OCP\BackgroundJob\IJob |string $job + * @param mixed $argument The argument to be passed to $job->run() when the job is exectured + */ + public function add($job, $argument = null); + + /** + * Remove a job from the list + * + * @param \OCP\BackgroundJob\IJob|string $job + * @param mixed $argument + */ + public function remove($job, $argument = null); + + /** + * check if a job is in the list + * + * @param $job + * @param mixed $argument + * @return bool + */ + public function has($job, $argument); + + /** + * get all jobs in the list + * + * @return \OCP\BackgroundJob\IJob[] + */ + public function getAll(); + + /** + * get the next job in the list + * + * @return \OCP\BackgroundJob\IJob + */ + public function getNext(); + + /** + * @param int $id + * @return \OCP\BackgroundJob\IJob + */ + public function getById($id); + + /** + * set the job that was last ran to the current time + * + * @param \OCP\BackgroundJob\IJob $job + */ + public function setLastJob($job); + + /** + * get the id of the last ran job + * + * @return int + */ + public function getLastJob(); + + /** + * set the lastRun of $job to now + * + * @param \OCP\BackgroundJob\IJob $job + */ + public function setLastRun($job); +} diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index dcb00caf49..5fb51f9ecd 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -183,4 +183,11 @@ interface IServerContainer { */ function getAvatarManager(); + /** + * Returns an job list for controlling background jobs + * + * @return \OCP\BackgroundJob\IJobList + */ + function getJobList(); + } diff --git a/tests/lib/backgroundjob/dummyjoblist.php b/tests/lib/backgroundjob/dummyjoblist.php index e1579c273b..7801269b27 100644 --- a/tests/lib/backgroundjob/dummyjoblist.php +++ b/tests/lib/backgroundjob/dummyjoblist.php @@ -21,6 +21,8 @@ class DummyJobList extends \OC\BackgroundJob\JobList { private $last = 0; + public function __construct(){} + /** * @param \OC\BackgroundJob\Job|string $job * @param mixed $argument diff --git a/tests/lib/backgroundjob/job.php b/tests/lib/backgroundjob/job.php index 7d66fa772d..10a8f46462 100644 --- a/tests/lib/backgroundjob/job.php +++ b/tests/lib/backgroundjob/job.php @@ -8,31 +8,6 @@ namespace Test\BackgroundJob; - -class TestJob extends \OC\BackgroundJob\Job { - private $testCase; - - /** - * @var callable $callback - */ - private $callback; - - /** - * @param Job $testCase - * @param callable $callback - */ - public function __construct($testCase, $callback) { - $this->testCase = $testCase; - $this->callback = $callback; - } - - public function run($argument) { - $this->testCase->markRun(); - $callback = $this->callback; - $callback($argument); - } -} - class Job extends \PHPUnit_Framework_TestCase { private $run = false; diff --git a/tests/lib/backgroundjob/joblist.php b/tests/lib/backgroundjob/joblist.php new file mode 100644 index 0000000000..c3318f80cb --- /dev/null +++ b/tests/lib/backgroundjob/joblist.php @@ -0,0 +1,203 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\BackgroundJob; + +class JobList extends \PHPUnit_Framework_TestCase { + /** + * @var \OC\BackgroundJob\JobList + */ + protected $instance; + + /** + * @var \OCP\IConfig | \PHPUnit_Framework_MockObject_MockObject $config + */ + protected $config; + + public function setUp() { + $conn = \OC::$server->getDatabaseConnection(); + $this->config = $this->getMock('\OCP\IConfig'); + $this->instance = new \OC\BackgroundJob\JobList($conn, $this->config); + } + + public function argumentProvider() { + return array( + array(null), + array(false), + array('foobar'), + array(12), + array(array( + 'asd' => 5, + 'foo' => 'bar' + )) + ); + } + + /** + * @dataProvider argumentProvider + * @param $argument + */ + public function testAddRemove($argument) { + $existingJobs = $this->instance->getAll(); + $job = new TestJob(); + $this->instance->add($job, $argument); + + $jobs = $this->instance->getAll(); + + $this->assertCount(count($existingJobs) + 1, $jobs); + $addedJob = $jobs[count($jobs) - 1]; + $this->assertInstanceOf('\Test\BackgroundJob\TestJob', $addedJob); + $this->assertEquals($argument, $addedJob->getArgument()); + + $this->instance->remove($job, $argument); + + $jobs = $this->instance->getAll(); + $this->assertEquals($existingJobs, $jobs); + } + + /** + * @dataProvider argumentProvider + * @param $argument + */ + public function testRemoveDifferentArgument($argument) { + $existingJobs = $this->instance->getAll(); + $job = new TestJob(); + $this->instance->add($job, $argument); + + $jobs = $this->instance->getAll(); + $this->instance->remove($job, 10); + $jobs2 = $this->instance->getAll(); + + $this->assertEquals($jobs, $jobs2); + + $this->instance->remove($job, $argument); + + $jobs = $this->instance->getAll(); + $this->assertEquals($existingJobs, $jobs); + } + + /** + * @dataProvider argumentProvider + * @param $argument + */ + public function testHas($argument) { + $job = new TestJob(); + $this->assertFalse($this->instance->has($job, $argument)); + $this->instance->add($job, $argument); + + $this->assertTrue($this->instance->has($job, $argument)); + + $this->instance->remove($job, $argument); + + $this->assertFalse($this->instance->has($job, $argument)); + } + + /** + * @dataProvider argumentProvider + * @param $argument + */ + public function testHasDifferentArgument($argument) { + $job = new TestJob(); + $this->instance->add($job, $argument); + + $this->assertFalse($this->instance->has($job, 10)); + + $this->instance->remove($job, $argument); + } + + public function testGetLastJob() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('backgroundjob', 'lastjob', 0) + ->will($this->returnValue(15)); + + $this->assertEquals(15, $this->instance->getLastJob()); + } + + public function testGetNext() { + $job = new TestJob(); + $this->instance->add($job, 1); + $this->instance->add($job, 2); + + $jobs = $this->instance->getAll(); + + $savedJob1 = $jobs[count($jobs) - 2]; + $savedJob2 = $jobs[count($jobs) - 1]; + + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('backgroundjob', 'lastjob', 0) + ->will($this->returnValue($savedJob1->getId())); + + $nextJob = $this->instance->getNext(); + + $this->assertEquals($savedJob2, $nextJob); + + $this->instance->remove($job, 1); + $this->instance->remove($job, 2); + } + + public function testGetNextWrapAround() { + $job = new TestJob(); + $this->instance->add($job, 1); + $this->instance->add($job, 2); + + $jobs = $this->instance->getAll(); + + $savedJob2 = $jobs[count($jobs) - 1]; + + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('backgroundjob', 'lastjob', 0) + ->will($this->returnValue($savedJob2->getId())); + + $nextJob = $this->instance->getNext(); + + $this->assertEquals($jobs[0], $nextJob); + + $this->instance->remove($job, 1); + $this->instance->remove($job, 2); + } + + /** + * @dataProvider argumentProvider + * @param $argument + */ + public function testGetById($argument) { + $job = new TestJob(); + $this->instance->add($job, $argument); + + $jobs = $this->instance->getAll(); + + $addedJob = $jobs[count($jobs) - 1]; + + $this->assertEquals($addedJob, $this->instance->getById($addedJob->getId())); + + $this->instance->remove($job, $argument); + } + + public function testSetLastRun() { + $job = new TestJob(); + $this->instance->add($job); + + $jobs = $this->instance->getAll(); + + $addedJob = $jobs[count($jobs) - 1]; + + $timeStart = time(); + $this->instance->setLastRun($addedJob); + $timeEnd = time(); + + $addedJob = $this->instance->getById($addedJob->getId()); + + $this->assertGreaterThanOrEqual($timeStart, $addedJob->getLastRun()); + $this->assertLessThanOrEqual($timeEnd, $addedJob->getLastRun()); + + $this->instance->remove($job); + } +} diff --git a/tests/lib/backgroundjob/testjob.php b/tests/lib/backgroundjob/testjob.php new file mode 100644 index 0000000000..23fc4268d1 --- /dev/null +++ b/tests/lib/backgroundjob/testjob.php @@ -0,0 +1,34 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\BackgroundJob; + + +class TestJob extends \OC\BackgroundJob\Job { + private $testCase; + + /** + * @var callable $callback + */ + private $callback; + + /** + * @param Job $testCase + * @param callable $callback + */ + public function __construct($testCase = null, $callback = null) { + $this->testCase = $testCase; + $this->callback = $callback; + } + + public function run($argument) { + $this->testCase->markRun(); + $callback = $this->callback; + $callback($argument); + } +}