├── .gitignore ├── LICENSE ├── README ├── README.textile ├── config ├── migrations │ ├── dump.php │ └── map.php └── sql │ └── queue.php ├── models └── queued_task.php ├── tests ├── cases │ └── models │ │ └── queued_task.test.php └── fixtures │ └── queued_task_fixture.php └── vendors └── shells ├── queue.php └── tasks ├── queue_email.php ├── queue_example.php └── queue_execute.php /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | *.bak -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 MGriesbach@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | -------------------------------- 2 | CakePHP Queue Plugin 3 | -------------------------------- 4 | http://github.com/MSeven/cakephp_queue 5 | 6 | Background: 7 | -------------------------------- 8 | This is a very simple and minimalistic job Queue (or deferred-task) system for CakePHP. 9 | 10 | Overall functionality is inspired by systems like Gearman, Beanstalk or dropr, but without any illusion to compete with these more advanced Systems. 11 | 12 | The Plugin is an attempt to provide a basic, simple to use method to enable deferred job execution, without the hassle of setting up or running an extra queue daemon, while integrating nicely into CakePHP and also simplifying the creation of worker scripts. 13 | 14 | Why use deferred execution? 15 | deferred execution makes sense (especially in PHP) when your page wants' to execute tasks, which are not directly related to rendering the current page. 16 | For instance, in a BBS-type system, a new users post might require the creation of multiple personalized email messages, notifying other users of the new content. 17 | Creating and sending these emails is completely irrelevant to the currently active user, and should not increase page response time. 18 | Another example would be downloading, extraction and/or analyzing an external file per request of the user. 19 | The regular solution to these problems would be to create specialized cronjobs which use specific database states to determine which action should be done. 20 | 21 | The Queue Plugin provides a simple method to create and run such non-user-interaction-critical tasks. 22 | 23 | While you can run multiple workers, and can (to some extend) spread these workers to different machines via a shared database, you should seriously consider using a more advanced system for high volume/high number of workers systems. 24 | 25 | Installation: 26 | -------------------------------- 27 | * Copy the files in this directory into /plugins/queue. 28 | * Run the following command in the cake console to create the tables: 29 | on Cakephp 1.2: 30 | @cake schema run create -path plugins\queue\config\sql -name queue@ 31 | on Cakephp 1.3 32 | @cake schema create -path plugins\queue\config\sql -name queue@ 33 | 34 | If you Installed the Queue Plugin in your global plugins directory instead of the app specific one, you will have to modify the -path parameter to point to the correct directory. 35 | 36 | Configuration: 37 | -------------------------------- 38 | The plugin allows some simple runtime configuration. 39 | You may create a file called "queue.php" inside your 'APP/config' folder (NOT the plugins config folder) to set the following values: 40 | 41 | #seconds to sleep() when no executable job is found 42 | $config['queue']['sleeptime'] = 10; 43 | 44 | #Propability in percent of a old job cleanup happening 45 | $config['queue']['gcprop'] = 10; 46 | 47 | #Default timeout after which a job is requeued if the worker doesn't report back 48 | $config['queue']['defaultworkertimeout'] = 120; 49 | 50 | #Default number of retries if a job fails or times out. 51 | $config['queue']['defaultworkerretries'] = 4; 52 | 53 | #Seconds of runnig time after which the worker will terminate (0 = unlimited) 54 | $config['queue']['workermaxruntime'] = 0; 55 | 56 | #Seconds after which finished jobs may be cleaned from the jobTable 57 | $config['queue']['cleanuptimeout'] = 2000; 58 | 59 | #Should a Workerprocess quit when there are no more tasks for it to execute (true = exit, false = keep running) 60 | $config['queue']['exitwhennothingtodo] = false 61 | 62 | The values above are the default settings which apply, when no configuration is found. 63 | 64 | Usage: 65 | -------------------------------- 66 | Run the following using the cakephp shell: 67 | 68 | cake queue help 69 | -> Display Help message 70 | cake queue add 71 | -> Try to call the cli add() function on a task 72 | -> tasks may or may not provide this functionality. 73 | cake queue runworker 74 | -> run a queue worker, which will look for a pending task it can execute. 75 | -> the worker will always try to find jobs matching its installed Tasks 76 | Notes: 77 | may either be the complete classname (eg. queue_example) 78 | or the shorthand without the leading "queue_" (eg. example) 79 | 80 | Use 'cake queue help' to get a list of installed/available tasks. 81 | 82 | Custom tasks should be placed in /vendors/shells/tasks. 83 | Tasks should be named 'queue_something.php' and implement a "queueSomethingTask", keeping Cakephp Naming conventions intact. 84 | 85 | A detailed Example task can be found in /vendors/shells/tasks/queue_example.php inside this folder. 86 | 87 | Current releases: http://github.com/MSeven/cakephp_queue/downloads 88 | Documentation: http://wiki.github.com/MSeven/cakephp_queue -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | 2 | h1. CakePHP Queue Plugin 3 | 4 | This is a very simple and minimalistic job Queue (or deferred-task) system for CakePHP. 5 | 6 | If this project is still relevant to you, please use the maintained fork here: https://github.com/dereuromark/cakephp-queue 7 | -------------------------------------------------------------------------------- /config/migrations/dump.php: -------------------------------------------------------------------------------- 1 | array( 20 | 'create_table' => array( 21 | 'queued_tasks' => array( 22 | 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), 23 | 'jobtype' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 45, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), 24 | 'data' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), 25 | 'group' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), 26 | 'reference' => array('type' => 'string', 'null' => true, 'default' => NULL, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), 27 | 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL), 28 | 'notbefore' => array('type' => 'datetime', 'null' => true, 'default' => NULL), 29 | 'fetched' => array('type' => 'datetime', 'null' => true, 'default' => NULL), 30 | 'completed' => array('type' => 'datetime', 'null' => true, 'default' => NULL), 31 | 'failed' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => 3), 32 | 'failure_message' => array('type' => 'text', 'null' => true, 'default' => NULL, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), 33 | 'workerkey' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 45, 'collate' => 'latin1_swedish_ci', 'charset' => 'latin1'), 34 | 'indexes' => array( 35 | 'PRIMARY' => array('column' => 'id', 'unique' => 1), 36 | ), 37 | 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM'), 38 | ), 39 | ), 40 | ), 41 | 'down' => array( 42 | 'drop_table' => array( 43 | 'queued_tasks' 44 | ), 45 | ), 46 | ); 47 | 48 | /** 49 | * Before migration callback 50 | * 51 | * @param string $direction, up or down direction of migration process 52 | * @return boolean Should process continue 53 | * @access public 54 | */ 55 | public function before($direction) { 56 | return true; 57 | } 58 | 59 | /** 60 | * After migration callback 61 | * 62 | * @param string $direction, up or down direction of migration process 63 | * @return boolean Should process continue 64 | * @access public 65 | */ 66 | public function after($direction) { 67 | return true; 68 | } 69 | } 70 | ?> -------------------------------------------------------------------------------- /config/migrations/map.php: -------------------------------------------------------------------------------- 1 | array( 4 | 'dump' => 'M4dd01d0d5ad8474c96b4476d67d6201c'), 5 | ); 6 | ?> -------------------------------------------------------------------------------- /config/sql/queue.php: -------------------------------------------------------------------------------- 1 | array( 22 | 'type' => 'integer', 23 | 'null' => false, 24 | 'default' => NULL, 25 | 'length' => 10, 26 | 'key' => 'primary' 27 | ), 28 | 'jobtype' => array( 29 | 'type' => 'string', 30 | 'null' => false, 31 | 'length' => 45 32 | ), 33 | 'data' => array( 34 | 'type' => 'text', 35 | 'null' => true, 36 | 'default' => NULL 37 | ), 38 | 'group' => array( 39 | 'type' => 'string', 40 | 'length' => 255, 41 | 'null' => true, 42 | 'default' => NULL 43 | ), 44 | 'reference' => array( 45 | 'type' => 'string', 46 | 'length' => 255, 47 | 'null' => true, 48 | 'default' => NULL 49 | ), 50 | 'created' => array( 51 | 'type' => 'datetime', 52 | 'null' => true, 53 | 'default' => NULL 54 | ), 55 | 'notbefore' => array( 56 | 'type' => 'datetime', 57 | 'null' => true, 58 | 'default' => NULL 59 | ), 60 | 'fetched' => array( 61 | 'type' => 'datetime', 62 | 'null' => true, 63 | 'default' => NULL 64 | ), 65 | 'completed' => array( 66 | 'type' => 'datetime', 67 | 'null' => true, 68 | 'default' => NULL 69 | ), 70 | 'failed' => array( 71 | 'type' => 'integer', 72 | 'null' => false, 73 | 'default' => '0', 74 | 'length' => 3 75 | ), 76 | 'failure_message' => array( 77 | 'type' => 'text', 78 | 'null' => true, 79 | 'default' => NULL 80 | ), 81 | 'workerkey' => array( 82 | 'type' => 'string', 83 | 'null' => true, 84 | 'length' => 45 85 | ), 86 | 'indexes' => array( 87 | 'PRIMARY' => array( 88 | 'column' => 'id', 89 | 'unique' => 1 90 | ) 91 | ) 92 | ); 93 | } 94 | ?> -------------------------------------------------------------------------------- /models/queued_task.php: -------------------------------------------------------------------------------- 1 | true 20 | ); 21 | 22 | /** 23 | * Add a new Job to the Queue 24 | * 25 | * @param string $jobName QueueTask name 26 | * @param array $data any array 27 | * @param string $group Used to group similar QueuedTasks 28 | * @param string $reference any array 29 | * @return bool success 30 | */ 31 | public function createJob($jobName, $data, $notBefore = null, $group = null, $reference = null) { 32 | 33 | $data = array( 34 | 'jobtype' => $jobName, 35 | 'data' => serialize($data), 36 | 'group' => $group, 37 | 'reference' => $reference 38 | ); 39 | if ($notBefore != null) { 40 | $data['notbefore'] = date('Y-m-d H:i:s', strtotime($notBefore)); 41 | } 42 | return ($this->save($this->create($data))); 43 | } 44 | 45 | public function onError() { 46 | $this->exit = true; 47 | } 48 | 49 | /** 50 | * Look for a new job that can be processed with the current abilities and 51 | * from the specified group (or any if null). 52 | * 53 | * @param array $capabilities Available QueueWorkerTasks. 54 | * @param string $group Request a job from this group, (from any group if null) 55 | * @return Array Taskdata. 56 | */ 57 | public function requestJob($capabilities, $group = null) { 58 | $idlist = array(); 59 | $wasFetched = array(); 60 | 61 | $findConf = array( 62 | 'conditions' => array( 63 | 'completed' => null, 64 | 'OR' => array() 65 | ), 66 | 'fields' => array( 67 | 'id', 68 | 'fetched', 69 | 'timediff(NOW(),notbefore) AS age' 70 | ), 71 | 'order' => array( 72 | 'age DESC', 73 | 'id ASC' 74 | ), 75 | 'limit' => 3 76 | ); 77 | 78 | if (!is_null($group)) { 79 | $findConf['conditions']['group'] = $group; 80 | } 81 | 82 | // generate the task specific conditions. 83 | foreach ($capabilities as $task) { 84 | $tmp = array( 85 | 'jobtype' => str_replace('queue_', '', $task['name']), 86 | 'AND' => array( 87 | array( 88 | 'OR' => array( 89 | 'notbefore <' => date('Y-m-d H:i:s'), 90 | 'notbefore' => null 91 | ) 92 | ), 93 | array( 94 | 'OR' => array( 95 | 'fetched <' => date('Y-m-d H:i:s', time() - $task['timeout']), 96 | 'fetched' => null 97 | ) 98 | ) 99 | ), 100 | 'failed <' => ($task['retries'] + 1) 101 | ); 102 | if (array_key_exists('rate', $task) && array_key_exists($tmp['jobtype'], $this->rateHistory)) { 103 | $tmp['NOW() >='] = date('Y-m-d H:i:s', $this->rateHistory[$tmp['jobtype']] + $task['rate']); 104 | } 105 | $findConf['conditions']['OR'][] = $tmp; 106 | } 107 | // First, find a list of a few of the oldest unfinished tasks. 108 | $data = $this->find('all', $findConf); 109 | if (is_array($data) && count($data) > 0) { 110 | // generate a list of their ID's 111 | foreach ($data as $item) { 112 | $idlist[] = $item[$this->name]['id']; 113 | if (!empty($item[$this->name]['fetched'])) { 114 | $wasFetched[] = $item[$this->name]['id']; 115 | } 116 | } 117 | // Generate a unique Identifier for the current worker thread 118 | $key = sha1(microtime()); 119 | // try to update one of the found tasks with the key of this worker. 120 | $this->query('UPDATE ' . $this->tablePrefix . $this->table . ' SET workerkey = "' . $key . '", fetched = "' . date('Y-m-d H:i:s') . '" WHERE id in(' . implode(',', $idlist) . ') AND (workerkey IS NULL OR fetched <= "' . date('Y-m-d H:i:s', time() - $task['timeout']) . '") ORDER BY timediff(NOW(),notbefore) DESC LIMIT 1'); 121 | // read which one actually got updated, which is the job we are supposed to execute. 122 | $data = $this->find('first', array( 123 | 'conditions' => array( 124 | 'workerkey' => $key 125 | ) 126 | )); 127 | if (is_array($data)) { 128 | // if the job had an existing fetched timestamp, increment the failure counter 129 | if (in_array($data[$this->name]['id'], $wasFetched)) { 130 | $data[$this->name]['failed']++; 131 | $data[$this->name]['failure_message'] = 'Restart after timeout'; 132 | $this->save($data); 133 | } 134 | //save last fetch by type for Rate Limiting. 135 | $this->rateHistory[$data[$this->name]['jobtype']] = time(); 136 | return $data[$this->name]; 137 | } 138 | } 139 | return FALSE; 140 | } 141 | 142 | /** 143 | * Mark a job as Completed, removing it from the queue. 144 | * 145 | * @param integer $id 146 | * @return bool Success 147 | */ 148 | public function markJobDone($id) { 149 | return ($this->updateAll(array( 150 | 'completed' => "'" . date('Y-m-d H:i:s') . "'" 151 | ), array( 152 | 'id' => $id 153 | ))); 154 | } 155 | 156 | /** 157 | * Mark a job as Failed, Incrementing the failed-counter and Requeueing it. 158 | * 159 | * @param integer $id 160 | * @param string $failureMessage Optional message to append to the 161 | * failure_message field 162 | */ 163 | public function markJobFailed($id, $failureMessage = null) { 164 | $db =& $this->getDataSource(); 165 | return ($this->updateAll(array( 166 | 'failed' => "failed + 1", 167 | 'failure_message' => $db->value($failureMessage, 'failure_message') 168 | ), array( 169 | 'id' => $id 170 | ))); 171 | } 172 | 173 | /** 174 | * Returns the number of items in the Queue. 175 | * Either returns the number of ALL pending tasks, or the number of pending tasks of the passed Type 176 | * 177 | * @param string $type jobType to Count 178 | * @return integer 179 | */ 180 | public function getLength($type = null) { 181 | $findConf = array( 182 | 'conditions' => array( 183 | 'completed' => null 184 | ) 185 | ); 186 | if ($type != NULL) { 187 | $findConf['conditions']['jobtype'] = $type; 188 | } 189 | return $this->find('count', $findConf); 190 | } 191 | 192 | /** 193 | * Return a list of all jobtypes in the Queue. 194 | * 195 | * @return array 196 | */ 197 | public function getTypes() { 198 | $findConf = array( 199 | 'fields' => array( 200 | 'jobtype' 201 | ), 202 | 'group' => array( 203 | 'jobtype' 204 | ) 205 | ); 206 | return $this->find('list', $findConf); 207 | } 208 | 209 | /** 210 | * Return some statistics about finished jobs still in the Database. 211 | * @return array 212 | */ 213 | public function getStats() { 214 | $findConf = array( 215 | 'fields' => array( 216 | 'jobtype,count(id) as num, AVG(UNIX_TIMESTAMP(completed)-UNIX_TIMESTAMP(created)) AS alltime, AVG(UNIX_TIMESTAMP(completed)-UNIX_TIMESTAMP(fetched)) AS runtime, AVG(UNIX_TIMESTAMP(fetched)-IF(notbefore is null,UNIX_TIMESTAMP(created),UNIX_TIMESTAMP(notbefore))) AS fetchdelay' 217 | ), 218 | 'conditions' => array( 219 | 'completed NOT' => null 220 | ), 221 | 'group' => array( 222 | 'jobtype' 223 | ) 224 | ); 225 | return $this->find('all', $findConf); 226 | } 227 | 228 | /** 229 | * Cleanup/Delete Completed Jobs. 230 | * 231 | */ 232 | public function cleanOldJobs() { 233 | $this->deleteAll(array( 234 | 'completed < ' => date('Y-m-d H:i:s', time() - Configure::read('queue.cleanuptimeout')) 235 | )); 236 | 237 | } 238 | 239 | protected function _findProgress($state, $query = array(), $results = array()) { 240 | if ($state == 'before') { 241 | 242 | $query['fields'] = array( 243 | $this->alias . '.reference', 244 | '(CASE WHEN ' . $this->alias . '.notbefore > NOW() THEN \'NOT_READY\' WHEN ' . $this->alias . '.fetched IS NULL THEN \'NOT_STARTED\' WHEN ' . $this->alias . '.fetched IS NOT NULL AND ' . $this->alias . '.completed IS NULL AND ' . $this->alias . '.failed = 0 THEN \'IN_PROGRESS\' WHEN ' . $this->alias . '.fetched IS NOT NULL AND ' . $this->alias . '.completed IS NULL AND ' . $this->alias . '.failed > 0 THEN \'FAILED\' WHEN ' . $this->alias . '.fetched IS NOT NULL AND ' . $this->alias . '.completed IS NOT NULL THEN \'COMPLETED\' ELSE \'UNKNOWN\' END) AS status', 245 | $this->alias . '.failure_message' 246 | ); 247 | if (isset($query['conditions']['exclude'])) { 248 | $exclude = $query['conditions']['exclude']; 249 | unset($query['conditions']['exclude']); 250 | $exclude = trim($exclude, ','); 251 | $exclude = explode(',', $exclude); 252 | $query['conditions'][] = array( 253 | 'NOT' => array( 254 | 'reference' => $exclude 255 | ) 256 | ); 257 | } 258 | if (isset($query['conditions']['group'])) { 259 | $query['conditions'][][$this->alias . '.group'] = $query['conditions']['group']; 260 | unset($query['conditions']['group']); 261 | } 262 | return $query; 263 | } else { 264 | foreach ($results as $k => $result) { 265 | $results[$k] = array( 266 | 'reference' => $result[$this->alias]['reference'], 267 | 'status' => $result[0]['status'] 268 | ); 269 | if (!empty($result[$this->alias]['failure_message'])) { 270 | $results[$k]['failure_message'] = $result[$this->alias]['failure_message']; 271 | } 272 | } 273 | return $results; 274 | } 275 | } 276 | 277 | public function clearDoublettes() { 278 | $x = $this->query('SELECT max(id) as id FROM `queueman`.`queued_tasks` 279 | where completed is null 280 | group by data 281 | having count(id) > 1'); 282 | 283 | $start = 0; 284 | $x = array_keys($x); 285 | while ($start <= count($x)) { 286 | debug($this->deleteAll(array( 287 | 'id' => array_slice($x, $start, 10) 288 | ))); 289 | debug(array_slice($x, $start, 10)); 290 | $start = $start + 100; 291 | } 292 | 293 | } 294 | } 295 | ?> -------------------------------------------------------------------------------- /tests/cases/models/queued_task.test.php: -------------------------------------------------------------------------------- 1 | QueuedTask = & ClassRegistry::init('TestQueuedTask'); 42 | } 43 | 44 | /** 45 | * Basic Instance test 46 | */ 47 | public function testQueueInstance() { 48 | $this->assertTrue(is_a($this->QueuedTask, 'TestQueuedTask')); 49 | } 50 | 51 | /** 52 | * Test the basic create and length evaluation functions. 53 | */ 54 | public function testCreateAndCount() { 55 | // at first, the queue should contain 0 items. 56 | $this->assertEqual(0, $this->QueuedTask->getLength()); 57 | 58 | // create a job 59 | $this->assertTrue($this->QueuedTask->createJob('test1', array( 60 | 'some' => 'random', 61 | 'test' => 'data' 62 | ))); 63 | 64 | // test if queue Length is 1 now. 65 | $this->assertEqual(1, $this->QueuedTask->getLength()); 66 | 67 | //create some more jobs 68 | $this->assertTrue($this->QueuedTask->createJob('test2', array( 69 | 'some' => 'random', 70 | 'test' => 'data2' 71 | ))); 72 | $this->assertTrue($this->QueuedTask->createJob('test2', array( 73 | 'some' => 'random', 74 | 'test' => 'data3' 75 | ))); 76 | $this->assertTrue($this->QueuedTask->createJob('test3', array( 77 | 'some' => 'random', 78 | 'test' => 'data4' 79 | ))); 80 | 81 | //overall queueLength shpould now be 4 82 | $this->assertEqual(4, $this->QueuedTask->getLength()); 83 | 84 | // there should be 1 task of type 'test1', one of type 'test3' and 2 of type 'test2' 85 | $this->assertEqual(1, $this->QueuedTask->getLength('test1')); 86 | $this->assertEqual(2, $this->QueuedTask->getLength('test2')); 87 | $this->assertEqual(1, $this->QueuedTask->getLength('test3')); 88 | } 89 | 90 | public function testCreateAndFetch() { 91 | //$capabilities is a list of tasks the worker can run. 92 | $capabilities = array( 93 | 'task1' => array( 94 | 'name' => 'task1', 95 | 'timeout' => 100, 96 | 'retries' => 2 97 | ) 98 | ); 99 | $testData = array( 100 | 'x1' => 'y1', 101 | 'x2' => 'y2', 102 | 'x3' => 'y3', 103 | 'x4' => 'y4' 104 | ); 105 | // start off empty. 106 | 107 | 108 | $this->assertEqual(array(), $this->QueuedTask->find('all')); 109 | // at first, the queue should contain 0 items. 110 | $this->assertEqual(0, $this->QueuedTask->getLength()); 111 | // there are no jobs, so we cant fetch any. 112 | $this->assertFalse($this->QueuedTask->requestJob($capabilities)); 113 | // insert one job. 114 | $this->assertTrue($this->QueuedTask->createJob('task1', $testData)); 115 | 116 | // fetch and check the first job. 117 | $data = $this->QueuedTask->requestJob($capabilities); 118 | $this->assertEqual(1, $data['id']); 119 | $this->assertEqual('task1', $data['jobtype']); 120 | $this->assertEqual(0, $data['failed']); 121 | $this->assertNull($data['completed']); 122 | $this->assertEqual($testData, unserialize($data['data'])); 123 | 124 | // after this job has been fetched, it may not be reassigned. 125 | $this->assertEqual(array(), $this->QueuedTask->requestJob($capabilities)); 126 | 127 | // queue length is still 1 since the first job did not finish. 128 | $this->assertEqual(1, $this->QueuedTask->getLength()); 129 | 130 | // Now mark Task1 as done 131 | $this->assertTrue($this->QueuedTask->markJobDone(1)); 132 | // Should be 0 again. 133 | $this->assertEqual(0, $this->QueuedTask->getLength()); 134 | } 135 | 136 | /** 137 | * Test the delivery of jobs in sequence, skipping fetched but not completed tasks. 138 | * 139 | */ 140 | public function testSequence() { 141 | //$capabilities is a list of tasks the worker can run. 142 | $capabilities = array( 143 | 'task1' => array( 144 | 'name' => 'task1', 145 | 'timeout' => 100, 146 | 'retries' => 2 147 | ) 148 | ); 149 | // at first, the queue should contain 0 items. 150 | $this->assertEqual(0, $this->QueuedTask->getLength()); 151 | // create some more jobs 152 | foreach (range(0, 9) as $num) { 153 | $this->assertTrue($this->QueuedTask->createJob('task1', array( 154 | 'tasknum' => $num 155 | ))); 156 | } 157 | // 10 jobs in the queue. 158 | $this->assertEqual(10, $this->QueuedTask->getLength()); 159 | 160 | // jobs should be fetched in the original sequence. 161 | foreach (range(0, 4) as $num) { 162 | $job = $this->QueuedTask->requestJob($capabilities); 163 | $jobData = unserialize($job['data']); 164 | $this->assertEqual($num, $jobData['tasknum']); 165 | } 166 | // now mark them as done 167 | foreach (range(0, 4) as $num) { 168 | $this->assertTrue($this->QueuedTask->markJobDone($num + 1)); 169 | $this->assertEqual(9 - $num, $this->QueuedTask->getLength()); 170 | } 171 | 172 | // jobs should be fetched in the original sequence. 173 | foreach (range(5, 9) as $num) { 174 | $job = $this->QueuedTask->requestJob($capabilities); 175 | $jobData = unserialize($job['data']); 176 | $this->assertEqual($num, $jobData['tasknum']); 177 | $this->assertTrue($this->QueuedTask->markJobDone($job['id'])); 178 | $this->assertEqual(9 - $num, $this->QueuedTask->getLength()); 179 | } 180 | } 181 | 182 | /** 183 | * Test creating Jobs to run close to a specified time, and strtotime parsing. 184 | * @return null 185 | */ 186 | public function testNotBefore() { 187 | $this->assertTrue($this->QueuedTask->createJob('task1', null, '+ 1 Min')); 188 | $this->assertTrue($this->QueuedTask->createJob('task1', null, '+ 1 Day')); 189 | $this->assertTrue($this->QueuedTask->createJob('task1', null, '2009-07-01 12:00:00')); 190 | $data = $this->QueuedTask->find('all'); 191 | $this->assertEqual($data[0]['TestQueuedTask']['notbefore'], date('Y-m-d H:i:s', strtotime('+ 1 Min'))); 192 | $this->assertEqual($data[1]['TestQueuedTask']['notbefore'], date('Y-m-d H:i:s', strtotime('+ 1 Day'))); 193 | $this->assertEqual($data[2]['TestQueuedTask']['notbefore'], '2009-07-01 12:00:00'); 194 | } 195 | 196 | /** 197 | * Test Job reordering depending on 'notBefore' field. 198 | * Jobs with an expired notbefore field should be executed before any other job without specific timing info. 199 | * @return null 200 | */ 201 | public function testNotBeforeOrder() { 202 | $capabilities = array( 203 | 'task1' => array( 204 | 'name' => 'task1', 205 | 'timeout' => 100, 206 | 'retries' => 2 207 | ), 208 | 'dummytask' => array( 209 | 'name' => 'dummytask', 210 | 'timeout' => 100, 211 | 'retries' => 2 212 | ) 213 | ); 214 | $this->assertTrue($this->QueuedTask->createJob('dummytask', null)); 215 | $this->assertTrue($this->QueuedTask->createJob('dummytask', null)); 216 | // create a task with it's execution target some seconds in the past, so it should jump to the top of the list. 217 | $this->assertTrue($this->QueuedTask->createJob('task1', 'three', '- 3 Seconds')); 218 | $this->assertTrue($this->QueuedTask->createJob('task1', 'two', '- 4 Seconds')); 219 | $this->assertTrue($this->QueuedTask->createJob('task1', 'one', '- 5 Seconds')); 220 | 221 | // when usin requestJob, the jobs we just created should be delivered in this order, NOT the order in which they where created. 222 | $expected = array( 223 | array( 224 | 'name' => 'task1', 225 | 'data' => 'one' 226 | ), 227 | array( 228 | 'name' => 'task1', 229 | 'data' => 'two' 230 | ), 231 | array( 232 | 'name' => 'task1', 233 | 'data' => 'three' 234 | ), 235 | array( 236 | 'name' => 'dummytask', 237 | 'data' => '' 238 | ), 239 | array( 240 | 'name' => 'dummytask', 241 | 'data' => '' 242 | ) 243 | ); 244 | 245 | foreach ($expected as $item) { 246 | $tmp = $this->QueuedTask->requestJob($capabilities); 247 | $this->assertEqual($item['name'], $tmp['jobtype']); 248 | $this->assertEqual($item['data'], unserialize($tmp['data'])); 249 | } 250 | } 251 | 252 | /** 253 | * Job Rate limiting. 254 | * Do not execute jobs of a certain type more often than once every X seconds. 255 | */ 256 | public function testRateLimit() { 257 | $capabilities = array( 258 | 'task1' => array( 259 | 'name' => 'task1', 260 | 'timeout' => 100, 261 | 'retries' => 2, 262 | 'rate' => 1 263 | ), 264 | 'dummytask' => array( 265 | 'name' => 'dummytask', 266 | 'timeout' => 100, 267 | 'retries' => 2 268 | ) 269 | ); 270 | 271 | // clear out the rate history 272 | $this->QueuedTask->rateHistory = array(); 273 | 274 | $this->assertTrue($this->QueuedTask->createJob('task1', '1')); 275 | $this->assertTrue($this->QueuedTask->createJob('task1', '2')); 276 | $this->assertTrue($this->QueuedTask->createJob('task1', '3')); 277 | $this->assertTrue($this->QueuedTask->createJob('dummytask', null)); 278 | $this->assertTrue($this->QueuedTask->createJob('dummytask', null)); 279 | $this->assertTrue($this->QueuedTask->createJob('dummytask', null)); 280 | $this->assertTrue($this->QueuedTask->createJob('dummytask', null)); 281 | 282 | //At first we get task1-1. 283 | $tmp = $this->QueuedTask->requestJob($capabilities); 284 | $this->assertEqual($tmp['jobtype'], 'task1'); 285 | $this->assertEqual(unserialize($tmp['data']), '1'); 286 | 287 | //The rate limit should now skip over task1-2 and fetch a dummytask. 288 | $tmp = $this->QueuedTask->requestJob($capabilities); 289 | $this->assertEqual($tmp['jobtype'], 'dummytask'); 290 | $this->assertEqual(unserialize($tmp['data']), null); 291 | 292 | //and again. 293 | $tmp = $this->QueuedTask->requestJob($capabilities); 294 | $this->assertEqual($tmp['jobtype'], 'dummytask'); 295 | $this->assertEqual(unserialize($tmp['data']), null); 296 | 297 | //Then some time passes 298 | sleep(1); 299 | 300 | //Now we should get task1-2 301 | $tmp = $this->QueuedTask->requestJob($capabilities); 302 | $this->assertEqual($tmp['jobtype'], 'task1'); 303 | $this->assertEqual(unserialize($tmp['data']), '2'); 304 | 305 | //and again rate limit to dummytask. 306 | $tmp = $this->QueuedTask->requestJob($capabilities); 307 | $this->assertEqual($tmp['jobtype'], 'dummytask'); 308 | $this->assertEqual(unserialize($tmp['data']), null); 309 | 310 | //Then some more time passes 311 | sleep(1); 312 | 313 | //Now we should get task1-3 314 | $tmp = $this->QueuedTask->requestJob($capabilities); 315 | $this->assertEqual($tmp['jobtype'], 'task1'); 316 | $this->assertEqual(unserialize($tmp['data']), '3'); 317 | 318 | //and again rate limit to dummytask. 319 | $tmp = $this->QueuedTask->requestJob($capabilities); 320 | $this->assertEqual($tmp['jobtype'], 'dummytask'); 321 | $this->assertEqual(unserialize($tmp['data']), null); 322 | 323 | //and now the queue is empty 324 | $tmp = $this->QueuedTask->requestJob($capabilities); 325 | $this->assertEqual($tmp, null); 326 | } 327 | 328 | public function testRequeueAfterTimeout() { 329 | $capabilities = array( 330 | 'task1' => array( 331 | 'name' => 'task1', 332 | 'timeout' => 1, 333 | 'retries' => 2, 334 | 'rate' => 0 335 | ) 336 | ); 337 | 338 | $this->assertTrue($this->QueuedTask->createJob('task1', '1')); 339 | $tmp = $this->QueuedTask->requestJob($capabilities); 340 | $this->assertEqual($tmp['jobtype'], 'task1'); 341 | $this->assertEqual(unserialize($tmp['data']), '1'); 342 | $this->assertEqual($tmp['failed'], '0'); 343 | sleep(2); 344 | $tmp = $this->QueuedTask->requestJob($capabilities); 345 | $this->assertEqual($tmp['jobtype'], 'task1'); 346 | $this->assertEqual(unserialize($tmp['data']), '1'); 347 | $this->assertEqual($tmp['failed'], '1'); 348 | $this->assertEqual($tmp['failure_message'], 'Restart after timeout'); 349 | } 350 | 351 | public function testRequestGroup() { 352 | $capabilities = array( 353 | 'task1' => array( 354 | 'name' => 'task1', 355 | 'timeout' => 1, 356 | 'retries' => 2, 357 | 'rate' => 0 358 | ) 359 | ); 360 | 361 | // create an ungrouped task 362 | $this->assertTrue($this->QueuedTask->createJob('task1', 1)); 363 | //create a Grouped Task 364 | $this->assertTrue($this->QueuedTask->createJob('task1', 2, null, 'testgroup')); 365 | 366 | // Fetching without group should completely ignore the Group field. 367 | $tmp = $this->QueuedTask->requestJob($capabilities); 368 | $this->assertEqual($tmp['jobtype'], 'task1'); 369 | $this->assertEqual(unserialize($tmp['data']), 1); 370 | $tmp = $this->QueuedTask->requestJob($capabilities); 371 | $this->assertEqual($tmp['jobtype'], 'task1'); 372 | $this->assertEqual(unserialize($tmp['data']), 2); 373 | 374 | // well, lets tra that Again, while limiting by Group 375 | // create an ungrouped task 376 | $this->assertTrue($this->QueuedTask->createJob('task1', 3)); 377 | //create a Grouped Task 378 | $this->assertTrue($this->QueuedTask->createJob('task1', 4, null, 'testgroup', 'Job number 4')); 379 | $this->assertTrue($this->QueuedTask->createJob('task1', 5, null, null, 'Job number 5')); 380 | $this->assertTrue($this->QueuedTask->createJob('task1', 6, null, 'testgroup', 'Job number 6')); 381 | 382 | // we should only get tasks 4 and 6, in that order, when requesting inside the group 383 | $tmp = $this->QueuedTask->requestJob($capabilities, 'testgroup'); 384 | $this->assertEqual($tmp['jobtype'], 'task1'); 385 | $this->assertEqual(unserialize($tmp['data']), 4); 386 | $tmp = $this->QueuedTask->requestJob($capabilities, 'testgroup'); 387 | $this->assertEqual($tmp['jobtype'], 'task1'); 388 | $this->assertEqual(unserialize($tmp['data']), 6); 389 | 390 | // use FindProgress on the testgroup: 391 | $progress = $this->QueuedTask->find('progress', array( 392 | 'conditions' => array( 393 | 'group' => 'testgroup' 394 | ) 395 | )); 396 | 397 | $this->assertEqual(count($progress), 3); 398 | 399 | $this->assertNull($progress[0]['reference']); 400 | $this->assertEqual($progress[0]['status'], 'IN_PROGRESS'); 401 | $this->assertEqual($progress[1]['reference'], 'Job number 4'); 402 | $this->assertEqual($progress[1]['status'], 'IN_PROGRESS'); 403 | $this->assertEqual($progress[2]['reference'], 'Job number 6'); 404 | $this->assertEqual($progress[2]['status'], 'IN_PROGRESS'); 405 | } 406 | 407 | public function testMarkJobFailed() { 408 | $this->QueuedTask->createJob('dummytask', null); 409 | $id = $this->QueuedTask->id; 410 | $expected = 'Timeout: 100'; 411 | $this->QueuedTask->markJobFailed($id, $expected); 412 | $result = $this->QueuedTask->field('failure_message'); 413 | $this->assertEqual($result, $expected); 414 | } 415 | 416 | } 417 | ?> -------------------------------------------------------------------------------- /tests/fixtures/queued_task_fixture.php: -------------------------------------------------------------------------------- 1 | array( 16 | 'type' => 'integer', 17 | 'null' => false, 18 | 'default' => NULL, 19 | 'length' => 10, 20 | 'key' => 'primary' 21 | ), 22 | 'jobtype' => array( 23 | 'type' => 'string', 24 | 'null' => false, 25 | 'length' => 45 26 | ), 27 | 'data' => array( 28 | 'type' => 'text', 29 | 'null' => true, 30 | 'default' => NULL 31 | ), 32 | 'group' => array( 33 | 'type' => 'string', 34 | 'length' => 255, 35 | 'null' => true, 36 | 'default' => NULL 37 | ), 38 | 'reference' => array( 39 | 'type' => 'string', 40 | 'length' => 255, 41 | 'null' => true, 42 | 'default' => NULL 43 | ), 44 | 'created' => array( 45 | 'type' => 'datetime', 46 | 'null' => false 47 | ), 48 | 'notbefore' => array( 49 | 'type' => 'datetime', 50 | 'null' => true, 51 | 'default' => NULL 52 | ), 53 | 'fetched' => array( 54 | 'type' => 'datetime', 55 | 'null' => true, 56 | 'default' => NULL 57 | ), 58 | 'completed' => array( 59 | 'type' => 'datetime', 60 | 'null' => true, 61 | 'default' => NULL 62 | ), 63 | 'failed' => array( 64 | 'type' => 'integer', 65 | 'null' => false, 66 | 'default' => '0', 67 | 'length' => 3 68 | ), 69 | 'failure_message' => array( 70 | 'type' => 'text', 71 | 'null' => true, 72 | 'default' => NULL 73 | ), 74 | 'workerkey' => array( 75 | 'type' => 'string', 76 | 'null' => true, 77 | 'length' => 45 78 | ), 79 | 'indexes' => array( 80 | 'PRIMARY' => array( 81 | 'column' => 'id', 82 | 'unique' => 1 83 | ) 84 | ) 85 | ); 86 | public $records = array(); 87 | 88 | } 89 | ?> -------------------------------------------------------------------------------- /vendors/shells/queue.php: -------------------------------------------------------------------------------- 1 | _loadModels(); 31 | 32 | foreach ($this->Dispatch->shellPaths as $path) { 33 | $folder = new Folder($path . DS . 'tasks'); 34 | $this->tasks = array_merge($this->tasks, $folder->find('queue_.*\.php')); 35 | } 36 | // strip the extension fom the found task(file)s 37 | foreach ($this->tasks as &$task) { 38 | $task = basename($task, '.php'); 39 | } 40 | 41 | //Config can be overwritten via local app config. 42 | Configure::load('queue'); 43 | 44 | $conf = Configure::read('queue'); 45 | if (!is_array($conf)) { 46 | $conf = array(); 47 | } 48 | //merge with default configuration vars. 49 | Configure::write('queue', array_merge(array( 50 | 'sleeptime' => 10, 51 | 'gcprop' => 10, 52 | 'defaultworkertimeout' => 120, 53 | 'defaultworkerretries' => 4, 54 | 'workermaxruntime' => 0, 55 | 'cleanuptimeout' => 2000, 56 | 'exitwhennothingtodo' => false 57 | ), $conf)); 58 | 59 | if(isset($this->params['-verbose'])) { 60 | $this->_verbose = true; 61 | } 62 | } 63 | 64 | /** 65 | * Output some basic usage Info. 66 | */ 67 | public function help() { 68 | $this->out('CakePHP Queue Plugin:'); 69 | $this->hr(); 70 | $this->out('Information goes here.'); 71 | $this->hr(); 72 | $this->out('Usage: cake queue ...'); 73 | $this->hr(); 74 | $this->out('Commands:'); 75 | $this->out(' queue help'); 76 | $this->out(' shows this help message.', 2); 77 | $this->out(' queue add '); 78 | $this->out(' tries to call the cli `add()` function on a task.'); 79 | $this->out(' tasks may or may not provide this functionality.', 2); 80 | $this->out(' cake queue runworker [--verbose]'); 81 | $this->out(' run a queue worker, which will look for a pending task it can execute.'); 82 | $this->out(' the worker will always try to find jobs matching its installed tasks.'); 83 | $this->out(' see "Available tasks" below.', 2); 84 | $this->out(' queue stats'); 85 | $this->out(' display some general statistics.', 2); 86 | $this->out(' queue clean'); 87 | $this->out(' manually call cleanup function to delete task data of completed tasks.', 2); 88 | $this->out('Note:'); 89 | $this->out(' may either be the complete classname (eg. `queue_example`)'); 90 | $this->out(' or the shorthand without the leading "queue_" (eg. `example`).', 2); 91 | $this->_listTasks(); 92 | } 93 | 94 | /** 95 | * Look for a Queue Task of hte passed name and try to call add() on it. 96 | * A QueueTask may provide an add function to enable the user to create new jobs via commandline. 97 | * 98 | */ 99 | public function add() { 100 | if (count($this->args) < 1) { 101 | $this->out('Usage:'); 102 | $this->out(' cake queue add ', 2); 103 | $this->_listTasks(); 104 | } else { 105 | if (in_array($this->args[0], $this->taskNames)) { 106 | $this->{$this->args[0]}->add(); 107 | } elseif (in_array('queue_' . $this->args[0], $this->taskNames)) { 108 | $this->{'queue_' . $this->args[0]}->add(); 109 | } else { 110 | $this->out('Error:'); 111 | $this->out(' Task not found: ' . $this->args[0], 2); 112 | $this->_listTasks(); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Run a QueueWorker loop. 119 | * Runs a Queue Worker process which will try to find unassigned jobs in the queue 120 | * which it may run and try to fetch and execute them. 121 | */ 122 | public function runworker() { 123 | // Enable Garbage Collector (PHP >= 5.3) 124 | if (function_exists('gc_enable')) { 125 | gc_enable(); 126 | } 127 | $exit = false; 128 | $starttime = time(); 129 | $group = null; 130 | if (isset($this->params['group']) && !empty($this->params['group'])) { 131 | $group = $this->params['group']; 132 | } 133 | while (!$exit) { 134 | if($this->_verbose) { 135 | $this->out('Looking for Job....'); 136 | } 137 | $data = $this->QueuedTask->requestJob($this->getTaskConf(), $group); 138 | if ($this->QueuedTask->exit === true) { 139 | $exit = true; 140 | } else { 141 | if ($data !== false) { 142 | $this->out('Running Job of type "' . $data['jobtype'] . '"'); 143 | $taskname = 'queue_' . strtolower($data['jobtype']); 144 | $return = $this->{$taskname}->run(unserialize($data['data'])); 145 | if ($return == true) { 146 | $this->QueuedTask->markJobDone($data['id']); 147 | $this->out('Job Finished.'); 148 | } else { 149 | $failureMessage = null; 150 | if (isset($this->{$taskname}->failureMessage) && !empty($this->{$taskname}->failureMessage)) { 151 | $failureMessage = $this->{$taskname}->failureMessage; 152 | } 153 | $this->QueuedTask->markJobFailed($data['id'], $failureMessage); 154 | $this->out('Job did not finish, requeued.'); 155 | } 156 | } elseif (Configure::read('queue.exitwhennothingtodo')) { 157 | $this->out('nothing to do, exiting.'); 158 | $exit = true; 159 | } else { 160 | if($this->_verbose) { 161 | $this->out('nothing to do, sleeping.'); 162 | } 163 | sleep(Configure::read('queue.sleeptime')); 164 | } 165 | 166 | // check if we are over the maximum runtime and end processing if so. 167 | if (Configure::read('queue.workermaxruntime') != 0 && (time() - $starttime) >= Configure::read('queue.workermaxruntime')) { 168 | $exit = true; 169 | $this->out('Reached runtime of ' . (time() - $starttime) . ' Seconds (Max ' . Configure::read('queue.workermaxruntime') . '), terminating.'); 170 | } 171 | if ($exit || rand(0, 100) > (100 - Configure::read('queue.gcprop'))) { 172 | $this->out('Performing Old job cleanup.'); 173 | $this->QueuedTask->cleanOldJobs(); 174 | } 175 | if($this->_verbose) { 176 | $this->hr(); 177 | } 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Manually trigger a Finished job cleanup. 184 | * @return null 185 | */ 186 | public function clean() { 187 | $this->out('Deleting old jobs, that have finished before ' . date('Y-m-d H:i:s', time() - Configure::read('queue.cleanuptimeout'))); 188 | $this->QueuedTask->cleanOldJobs(); 189 | } 190 | 191 | /** 192 | * Display Some statistics about Finished Jobs. 193 | * @return null 194 | */ 195 | public function stats() { 196 | $this->out('Jobs currenty in the Queue:'); 197 | 198 | $types = $this->QueuedTask->getTypes(); 199 | 200 | foreach ($types as $type) { 201 | $this->out(" " . str_pad($type, 20, ' ', STR_PAD_RIGHT) . ": " . $this->QueuedTask->getLength($type)); 202 | } 203 | $this->hr(); 204 | $this->out('Total unfinished Jobs : ' . $this->QueuedTask->getLength()); 205 | $this->hr(); 206 | $this->out('Finished Job Statistics:'); 207 | $data = $this->QueuedTask->getStats(); 208 | foreach ($data as $item) { 209 | $this->out(" " . $item['QueuedTask']['jobtype'] . ": "); 210 | $this->out(" Finished Jobs in Database: " . $item[0]['num']); 211 | $this->out(" Average Job existence : " . $item[0]['alltime'] . 's'); 212 | $this->out(" Average Execution delay : " . $item[0]['fetchdelay'] . 's'); 213 | $this->out(" Average Execution time : " . $item[0]['runtime'] . 's'); 214 | } 215 | } 216 | 217 | /** 218 | * Returns a List of available QueueTasks and their individual configurations. 219 | * @return array 220 | */ 221 | private function getTaskConf() { 222 | if (!is_array($this->taskConf)) { 223 | $this->taskConf = array(); 224 | foreach ($this->tasks as $task) { 225 | $this->taskConf[$task]['name'] = $task; 226 | if (property_exists($this->{$task}, 'timeout')) { 227 | $this->taskConf[$task]['timeout'] = $this->{$task}->timeout; 228 | } else { 229 | $this->taskConf[$task]['timeout'] = Configure::read('queue.defaultworkertimeout'); 230 | } 231 | if (property_exists($this->{$task}, 'retries')) { 232 | $this->taskConf[$task]['retries'] = $this->{$task}->retries; 233 | } else { 234 | $this->taskConf[$task]['retries'] = Configure::read('queue.defaultworkerretries'); 235 | } 236 | if (property_exists($this->{$task}, 'rate')) { 237 | $this->taskConf[$task]['rate'] = $this->{$task}->rate; 238 | } 239 | } 240 | } 241 | return $this->taskConf; 242 | } 243 | /** 244 | * Output a list of available tasks. 245 | */ 246 | protected function _listTasks() { 247 | $this->out('Available tasks:'); 248 | foreach ($this->taskNames as $loadedTask) { 249 | $this->out(' - ' . $loadedTask); 250 | } 251 | } 252 | 253 | function out($str='') { 254 | $str = date('Y-m-d H:i:s').' '.$str; 255 | return parent::out($str); 256 | } 257 | 258 | } 259 | ?> -------------------------------------------------------------------------------- /vendors/shells/tasks/queue_email.php: -------------------------------------------------------------------------------- 1 | null, 21 | 'subject' => null, 22 | 'charset' => 'UTF-8', 23 | 'from' => null, 24 | 'sendAs' => 'html', 25 | 'template' => null, 26 | 'debug' => false, 27 | 'additionalParams' => '', 28 | 'layout' => 'default' 29 | ); 30 | public $timeout = 120; 31 | public $retries = 0; 32 | /** 33 | * Controller class 34 | * 35 | * @var Controller 36 | */ 37 | public $Controller; 38 | 39 | /** 40 | * EmailComponent 41 | * 42 | * @var EmailComponent 43 | */ 44 | public $Email; 45 | 46 | public function add() { 47 | $this->err('Queue Email Task cannot be added via Console.'); 48 | $this->out('Please use createJob() on the QueuedTask Model to create a Proper Email Task.'); 49 | $this->out('The Data Array should look something like this:'); 50 | $this->out(var_export(array( 51 | 'settings' => array( 52 | 'to' => 'email@example.com', 53 | 'subject' => 'Email Subject', 54 | 'from' => 'system@example.com', 55 | 'template' => 'sometemplate' 56 | ), 57 | 'vars' => array( 58 | 'text' => 'hello world' 59 | ) 60 | ), true)); 61 | } 62 | 63 | public function run($data) { 64 | $this->Controller = & new Controller(); 65 | $this->Email = & new EmailComponent(null); 66 | $this->Email->initialize($this->Controller, $this->defaults); 67 | if (array_key_exists('settings', $data)) { 68 | $this->Email->_set(array_filter(am($this->defaults, $data['settings']))); 69 | if (array_key_exists('vars', $data)) { 70 | foreach ($data['vars'] as $name => $var) { 71 | $this->Controller->set($name, $var); 72 | } 73 | } 74 | return ($this->Email->send()); 75 | } 76 | $this->err('Queue Email task called without settings data.'); 77 | return false; 78 | } 79 | } 80 | ?> -------------------------------------------------------------------------------- /vendors/shells/tasks/queue_example.php: -------------------------------------------------------------------------------- 1 | out('CakePHP Queue Example task.'); 57 | $this->hr(); 58 | $this->out('This is a very simple example of a queueTask.'); 59 | $this->out('I will now add an example Job into the Queue.'); 60 | $this->out('This job will only produce some console output on the worker that it runs on.'); 61 | $this->out(' '); 62 | $this->out('To run a Worker use:'); 63 | $this->out(' cake queue runworker'); 64 | $this->out(' '); 65 | $this->out('You can find the sourcecode of this task in: '); 66 | $this->out(__FILE__); 67 | $this->out(' '); 68 | /** 69 | * Adding a task of type 'example' with no additionally passed data 70 | */ 71 | if ($this->QueuedTask->createJob('example', null)) { 72 | $this->out('OK, job created, now run the worker'); 73 | } else { 74 | $this->err('Could not create Job'); 75 | } 76 | } 77 | 78 | /** 79 | * Example run function. 80 | * This function is executed, when a worker is executing a task. 81 | * The return parameter will determine, if the task will be marked completed, or be requeued. 82 | * 83 | * @param array $data the array passed to QueuedTask->createJob() 84 | * @return bool Success 85 | */ 86 | public function run($data) { 87 | $this->hr(); 88 | $this->out('CakePHP Queue Example task.'); 89 | $this->hr(); 90 | $this->out(' ->Success, the Example Job was run.<-'); 91 | $this->out(' '); 92 | $this->out(' '); 93 | return true; 94 | } 95 | } 96 | ?> -------------------------------------------------------------------------------- /vendors/shells/tasks/queue_execute.php: -------------------------------------------------------------------------------- 1 | out('CakePHP Queue Execute task.'); 57 | $this->hr(); 58 | if (count($this->args) < 2) { 59 | $this->out('This will run an shell command on the Server.'); 60 | $this->out('The task is mainly intended to serve as a kind of buffer for programm calls from a cakephp application.'); 61 | $this->out(' '); 62 | $this->out('Call like this:'); 63 | $this->out(' cake queue add execute *command* *param1* *param2* ...'); 64 | $this->out(' '); 65 | } else { 66 | 67 | $data = array( 68 | 'command' => $this->args[1], 69 | 'params' => array_slice($this->args, 2) 70 | ); 71 | if ($this->QueuedTask->createJob('execute', $data)) { 72 | $this->out('Job created'); 73 | } else { 74 | $this->err('Could not create Job'); 75 | } 76 | 77 | } 78 | } 79 | 80 | /** 81 | * Run function. 82 | * This function is executed, when a worker is executing a task. 83 | * The return parameter will determine, if the task will be marked completed, or be requeued. 84 | * 85 | * @param array $data the array passed to QueuedTask->createJob() 86 | * @return bool Success 87 | */ 88 | public function run($data) { 89 | $command = escapeshellcmd($data['command']) . ' ' . implode(' ', $data['params']); 90 | $this->out('Executing: ' . $command); 91 | exec($command, $output, $status); 92 | $this->out(' '); 93 | $this->out($output); 94 | return (!$status); 95 | } 96 | } 97 | ?> --------------------------------------------------------------------------------