├── Lib
├── empty
├── QueueUtil.php
└── Queue.php
├── Vendor
└── empty
├── webroot
└── empty
├── Test
├── Fixture
│ ├── empty
│ ├── QueueTaskLogFixture.php
│ └── QueueTaskFixture.php
└── Case
│ ├── View
│ └── Helper
│ │ └── empty
│ ├── Model
│ ├── Behavior
│ │ └── empty
│ └── QueueTaskTest.php
│ └── Controller
│ ├── Component
│ └── empty
│ └── QueueTasksControllerTest.php
├── View
├── Helper
│ └── empty
└── QueueTasks
│ ├── admin_add.ctp
│ ├── admin_edit.ctp
│ ├── admin_view.ctp
│ └── admin_index.ctp
├── Config
├── Schema
│ ├── empty
│ └── schema.php
└── queue.php.default
├── Model
├── Behavior
│ └── empty
├── Datasource
│ └── empty
├── QueueTaskLog.php
├── QueueAppModel.php
└── QueueTask.php
├── .gitignore
├── Console
└── Command
│ ├── Task
│ └── empty
│ └── QueueShell.php
├── Controller
├── Component
│ └── empty
├── QueueAppController.php
└── QueueTasksController.php
├── scripts
└── process.sh
├── composer.json
└── readme.markdown
/Lib/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Vendor/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webroot/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Test/Fixture/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/View/Helper/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Config/Schema/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Model/Behavior/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Model/Datasource/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | notes.txt
2 |
--------------------------------------------------------------------------------
/Console/Command/Task/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Controller/Component/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Test/Case/View/Helper/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Test/Case/Model/Behavior/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Test/Case/Controller/Component/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Controller/QueueAppController.php:
--------------------------------------------------------------------------------
1 | > /tmp/queue_process_outpt.log
5 |
--------------------------------------------------------------------------------
/Config/queue.php.default:
--------------------------------------------------------------------------------
1 | array(
9 | 'log' => true, //logs every task run in a log file.
10 | 'limit' => 1, //limit how many queues can run at a time. (default 1).
11 | 'allowedTypes' => array( //restrict what type of command can be queued.
12 | 1, //model
13 | 2, //shell
14 | 3, //url
15 | //4, //php_cmd
16 | //5, //shell_cmd
17 | ),
18 | 'archiveAfterExecute' => true, //will archive the task once finished executing for quicker queues
19 | 'cache' => '+5 minute', //how long to cache cpu usage and list. (false disables cache)
20 | 'cores' => '8', //number of cpu cores to correctly gauge current CPU load.
21 | 'userIdMethod' => false, //Function to call on QueueAppModel to obtain the value for user_id (ships with _getUserId, defaults to off)
22 | )
23 | );
24 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webtechnick/cakephp-queue-plugin",
3 | "description": "CakePHP Queue Plugin - Complete tool to background and schedule your time consuming tasks. Queue up almost anything from CakePHP shells, models, and actions to standard php commands or even shell commands.",
4 | "type": "cakephp-plugin",
5 | "keywords": ["cakephp", "background", "tasks", "crons", "plugin"],
6 | "homepage": "https://github.com/webtechnick/CakePHP-Queue-Plugin",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Nick Baker",
11 | "homepage": "http://www.webtechnick.com",
12 | "role": "Author"
13 | }
14 | ],
15 | "support": {
16 | "issues": "https://github.com/webtechnick/CakePHP-Queue-Plugin/issues",
17 | "irc": "irc://irc.freenode.org/cakephp",
18 | "source": "https://github.com/webtechnick/CakePHP-Queue-Plugin"
19 | },
20 | "require": {
21 | "php": ">=5.3.0",
22 | "composer/installers": "*"
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "2.x-dev"
27 | },
28 | "installer-name": "Queue"
29 | }
30 | }
--------------------------------------------------------------------------------
/View/QueueTasks/admin_add.ctp:
--------------------------------------------------------------------------------
1 |
2 | Form->create('QueueTask'); ?>
3 |
4 |
5 | Form->input('user_id');
7 | echo $this->Form->input('executed');
8 | echo $this->Form->input('scheduled');
9 | echo $this->Form->input('scheduled_end');
10 | echo $this->Form->input('reschedule');
11 | echo $this->Form->input('start_time');
12 | echo $this->Form->input('end_time');
13 | echo $this->Form->input('cpu_limit');
14 | echo $this->Form->input('is_restricted');
15 | echo $this->Form->input('priority');
16 | echo $this->Form->input('status');
17 | echo $this->Form->input('type');
18 | echo $this->Form->input('command');
19 | echo $this->Form->input('result');
20 | ?>
21 |
22 | Form->end(__('Submit')); ?>
23 |
24 |
25 |
26 |
27 |
28 | Html->link(__('List Queue Tasks'), array('action' => 'index')); ?>
29 |
30 |
31 |
--------------------------------------------------------------------------------
/View/QueueTasks/admin_edit.ctp:
--------------------------------------------------------------------------------
1 |
2 | Form->create('QueueTask'); ?>
3 |
4 |
5 | Form->input('id');
7 | echo $this->Form->input('user_id');
8 | echo $this->Form->input('executed');
9 | echo $this->Form->input('scheduled');
10 | echo $this->Form->input('scheduled_end');
11 | echo $this->Form->input('reschedule');
12 | echo $this->Form->input('start_time');
13 | echo $this->Form->input('end_time');
14 | echo $this->Form->input('cpu_limit');
15 | echo $this->Form->input('is_restricted');
16 | echo $this->Form->input('priority');
17 | echo $this->Form->input('status');
18 | echo $this->Form->input('type');
19 | echo $this->Form->input('command');
20 | echo $this->Form->input('result');
21 | ?>
22 |
23 | Form->end(__('Submit')); ?>
24 |
25 |
26 |
27 |
28 |
29 | Form->postLink(__('Delete'), array('action' => 'delete', $this->Form->value('QueueTask.id')), array(), __('Are you sure you want to delete # %s?', $this->Form->value('QueueTask.id'))); ?>
30 | Html->link(__('List Queue Tasks'), array('action' => 'index')); ?>
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Model/QueueTaskLog.php:
--------------------------------------------------------------------------------
1 | 'QueueTaskLog.end_time - QueueTaskLog.start_time'
18 | );
19 |
20 | /**
21 | * Generate filter conditions for filter search
22 | * @param string filter
23 | * @param string pre character for search (default '') optional '%'
24 | */
25 | public function generateFilterConditions($filter = null, $pre = '') {
26 | $conditions = parent::generateFilterConditions($filter, $pre);
27 | foreach ($this->_statuses as $key => $name) {
28 | if (strtolower($filter) == $name) {
29 | $conditions['OR']["{$this->alias}.status"] = $key;
30 | unset($conditions['OR']["{$this->alias}.status LIKE"]);
31 | }
32 | }
33 | foreach ($this->_types as $key => $name) {
34 | if (strtolower($filter) == $name) {
35 | $conditions['OR']["{$this->alias}.type"] = $key;
36 | unset($conditions['OR']["{$this->alias}.type LIKE"]);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Test/Case/Controller/QueueTasksControllerTest.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Time->niceShort($queueTask['QueueTask']['created']); ?>
17 |
18 |
19 |
20 |
21 | Time->niceShort($queueTask['QueueTask']['modified']); ?>
22 |
23 |
24 |
25 |
26 | Time->niceShort($queueTask['QueueTask']['executed']); ?>
27 |
28 |
29 |
30 |
31 | Time->niceShort($queueTask['QueueTask']['scheduled']); ?>
32 |
33 |
34 |
35 |
36 | Time->niceShort($queueTask['QueueTask']['scheduled_end']); ?>
37 |
38 |
39 |
40 |
41 | Time->niceShort($queueTask['QueueTask']['reschedule']); ?>
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | Html->link(__('Edit Queue Task'), array('action' => 'edit', $queueTask['QueueTask']['id'])); ?>
95 | Html->link(__('List Queue Tasks'), array('action' => 'index')); ?>
96 | Html->link(__('New Queue Task'), array('action' => 'add')); ?>
97 |
98 |
99 |
--------------------------------------------------------------------------------
/Test/Fixture/QueueTaskLogFixture.php:
--------------------------------------------------------------------------------
1 | array('type' => 'string', 'null' => false, 'default' => null, 'length' => 36, 'key' => 'primary', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
15 | 'user_id' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'user_id of who created/modified this queue. optional'),
16 | 'created' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index'),
17 | 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null),
18 | 'executed' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'datetime when executed.'),
19 | 'scheduled' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'When the task is scheduled. if null as soon as possible. Otherwise it will be first on list if it\'s the highest scheduled.'),
20 | 'scheduled_end' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'If we go past this time, don\'t execute. We need to reschedule based on reschedule.'),
21 | 'reschedule' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 50, 'collate' => 'utf8_general_ci', 'comment' => 'strtotime parsable addition to scheduled until in future if window is not null.', 'charset' => 'utf8'),
22 | 'start_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime start of execution.'),
23 | 'end_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime end of execution.'),
24 | 'cpu_limit' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 3, 'key' => 'index', 'comment' => 'percent limit of cpu to execute. (95 = less than 95% cpu usage)'),
25 | 'is_restricted' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'key' => 'index', 'comment' => 'will be 1 if hour, day, or cpu_limit are not null.'),
26 | 'priority' => array('type' => 'integer', 'null' => false, 'default' => '100', 'length' => 4, 'key' => 'index', 'comment' => 'priorty, lower the number, the higher on the list it will run.'),
27 | 'status' => array('type' => 'integer', 'null' => false, 'default' => '1', 'length' => 2, 'key' => 'index', 'comment' => '1:queued,2:inprogress,3:finished,4:paused'),
28 | 'type' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 2, 'key' => 'index', 'comment' => '1:model,2:shell,3:url,4:php_cmd,5:shell_cmd'),
29 | 'command' => array('type' => 'text', 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
30 | 'result' => array('type' => 'text', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
31 | 'indexes' => array(
32 | 'PRIMARY' => array('column' => 'id', 'unique' => 1),
33 | 'status' => array('column' => 'status', 'unique' => 0),
34 | 'type' => array('column' => 'type', 'unique' => 0),
35 | 'created' => array('column' => 'created', 'unique' => 0),
36 | 'priority' => array('column' => 'priority', 'unique' => 0),
37 | 'is_restricted' => array('column' => 'is_restricted', 'unique' => 0),
38 | 'cpu_limit' => array('column' => 'cpu_limit', 'unique' => 0),
39 | 'executed' => array('column' => 'executed', 'unique' => 0),
40 | 'scheduled' => array('column' => 'scheduled', 'unique' => 0),
41 | 'scheduled_end' => array('column' => 'scheduled_end', 'unique' => 0)
42 | ),
43 | 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
44 | );
45 |
46 | /**
47 | * Records
48 | *
49 | * @var array
50 | */
51 | public $records = array(
52 | );
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Controller/QueueTasksController.php:
--------------------------------------------------------------------------------
1 | set('statuses', $this->QueueTask->_statuses);
24 | $this->set('types', $this->QueueTask->_types);
25 | }
26 |
27 | /**
28 | * admin_index method
29 | *
30 | * @return void
31 | */
32 | public function admin_index($filter = null) {
33 | if(!empty($this->request->data)){
34 | $filter = $this->request->data['QueueTask']['filter'];
35 | }
36 | $conditions = $this->QueueTask->generateFilterConditions($filter);
37 | $this->set('queueTasks',$this->paginate('QueueTask',$conditions));
38 | $this->set('filter', $filter);
39 | }
40 |
41 | public function admin_logs($filter = null) {
42 | if (!empty($this->request->data)) {
43 | $filter = $this->request->data['QueueTaskLog']['filter'];
44 | }
45 | $conditions = $this->QueueTaskLog->generateFilterConditions($filter);
46 | $this->set('queueTaskLogs',$this->paginate('QueueTaskLog',$conditions));
47 | $this->set('filter', $filter);
48 | }
49 |
50 | /**
51 | * admin_view method
52 | *
53 | * @throws NotFoundException
54 | * @param string $id
55 | * @return void
56 | */
57 | public function admin_view($id = null) {
58 | if (!$this->QueueTask->exists($id) && !$this->QueueTaskLog->exists($id)) {
59 | throw new NotFoundException(__('Invalid queue task'));
60 | }
61 | $this->set('queueTask', $this->QueueTask->findForView($id));
62 | }
63 |
64 | public function admin_process() {
65 | //Process the queue
66 | }
67 |
68 | public function admin_run($id = null) {
69 | if (!$this->QueueTask->exists($id)) {
70 | throw new NotFoundException(__('Invalid queue task'));
71 | }
72 | if ($this->QueueTask->run($id)) {
73 | $this->Session->setFlash('Queue ' . $id . ' Ran Successfully.');
74 | } else {
75 | $this->Session->setFlash('Queue ' . $id . ' Failed to run.');
76 | }
77 | return $this->redirect(array('admin' => true, 'action' => 'view', $id));
78 | }
79 |
80 | /**
81 | * admin_edit method
82 | *
83 | * @throws NotFoundException
84 | * @param string $id
85 | * @return void
86 | */
87 | public function admin_edit($id = null) {
88 | if (!empty($this->request->data)) {
89 | if ($this->QueueTask->adminSave($this->request->data)) {
90 | $this->Session->setFlash(__('The queue task has been saved'));
91 | $this->redirect(array('action' => 'index'));
92 | } else {
93 | $this->Session->setFlash(__('The queue task could not be saved. Please, try again.'));
94 | }
95 | }
96 |
97 | if ($id && empty($this->request->data)){
98 | $this->request->data['QueueTask'] = $this->QueueTask->findById($id);
99 | $this->set('id', $id);
100 | }
101 |
102 | }
103 |
104 | /**
105 | * admin_delete method
106 | *
107 | * @throws NotFoundException
108 | * @throws MethodNotAllowedException
109 | * @param string $id
110 | * @return void
111 | */
112 | public function admin_delete($id = null) {
113 | $this->QueueTask->id = $id;
114 | if (!$this->QueueTask->exists()) {
115 | throw new NotFoundException(__('Invalid queue task'));
116 | }
117 | $this->request->onlyAllow('post', 'delete');
118 | if ($this->QueueTask->delete($id)) {
119 | $this->Session->setFlash(__('Queue task deleted'));
120 | } else {
121 | $this->Session->setFlash(__('Queue task was not deleted'));
122 | }
123 |
124 | $this->redirect(array('action' => 'index'));
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/View/QueueTasks/admin_index.ctp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Paginator->sort('id'); ?>
7 | Paginator->sort('user_id'); ?>
8 | Paginator->sort('created'); ?>
9 | Paginator->sort('modified'); ?>
10 | Paginator->sort('executed'); ?>
11 | Paginator->sort('scheduled'); ?>
12 | Paginator->sort('scheduled_end'); ?>
13 | Paginator->sort('reschedule'); ?>
14 | Paginator->sort('start_time'); ?>
15 | Paginator->sort('end_time'); ?>
16 | Paginator->sort('cpu_limit'); ?>
17 | Paginator->sort('is_restricted'); ?>
18 | Paginator->sort('priority'); ?>
19 | Paginator->sort('status'); ?>
20 | Paginator->sort('type'); ?>
21 | Paginator->sort('command'); ?>
22 | Paginator->sort('result'); ?>
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Html->link(__('View'), array('action' => 'view', $queueTask['QueueTask']['id'])); ?>
48 | Html->link(__('Edit'), array('action' => 'edit', $queueTask['QueueTask']['id'])); ?>
49 | Form->postLink(__('Delete'), array('action' => 'delete', $queueTask['QueueTask']['id']), array(), __('Are you sure you want to delete # %s?', $queueTask['QueueTask']['id'])); ?>
50 |
51 |
52 |
53 |
54 |
55 |
56 | Paginator->counter(array(
58 | 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
59 | ));
60 | ?>
61 |
62 | Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
64 | echo $this->Paginator->numbers(array('separator' => ''));
65 | echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
66 | ?>
67 |
68 |
69 |
70 |
71 |
72 | Html->link(__('New Queue Task'), array('action' => 'add')); ?>
73 |
74 |
75 |
--------------------------------------------------------------------------------
/Lib/QueueUtil.php:
--------------------------------------------------------------------------------
1 | 'File',
76 | 'duration' => $duration,
77 | 'path' => CACHE,
78 | 'prefix' => 'queue_'
79 | ));
80 | }
81 | }
82 | /**
83 | * Get the cache key config if we have cache setup
84 | * @param string key
85 | * @return mixed boolean false or
86 | */
87 | public static function readCache($key) {
88 | if (self::getConfig('cache')) {
89 | return Cache::read($key, 'queue');
90 | }
91 | return false;
92 | }
93 | /**
94 | * Write the cache if we have a cache setup
95 | * @param string key
96 | * @param mixed value
97 | * @return boolean success
98 | */
99 | public static function writeCache($key, $value) {
100 | if (self::getConfig('cache')) {
101 | return Cache::write($key, $value, 'queue');
102 | }
103 | return false;
104 | }
105 |
106 | /**
107 | * Clears all queue cache.
108 | * @return boolean success
109 | */
110 | public static function clearCache() {
111 | if (self::getConfig('cache')) {
112 | return Cache::clear(false, 'queue');
113 | }
114 | return true;
115 | }
116 |
117 | /**
118 | * Write log if we have logging on.
119 | * @param string message to write
120 | */
121 | public static function writeLog($message) {
122 | if (self::getConfig('log')) {
123 | CakeLog::write('queue', $message);
124 | }
125 | }
126 | /**
127 | * Get the current Cpu Usage as a percentages
128 | * Grabs from cache if we have it.
129 | * @throws Exception
130 | * @return float current cpu percentage.
131 | */
132 | public static function currentCpu() {
133 | if ($cpu = self::readCache('cpu')) {
134 | return $cpu;
135 | }
136 | $uptime = shell_exec('uptime');
137 | if (empty($uptime) || strpos($uptime, 'load') === false) {
138 | throw new Exception('Unable to retrieve load avearge from uptime.');
139 | }
140 | $uptime = explode(':', $uptime);
141 | $averages = trim(array_pop($uptime));
142 | list($min1, $min5, $min15) = explode(' ', $averages, 3);
143 | $cores = self::getConfig('cores');
144 | if (!$cores) {
145 | $cores = 1;
146 | }
147 | $percent = ($min5 / $cores) * 100;
148 | $percent = round($percent);
149 | self::writeCache('cpu', $percent);
150 |
151 | return $percent;
152 | }
153 | }
--------------------------------------------------------------------------------
/Lib/Queue.php:
--------------------------------------------------------------------------------
1 | add($command, $type, $options);
38 | }
39 |
40 | /**
41 | * Deletes a task from the queue.
42 | * @param string uuid
43 | * @param boolean force - if true will bypass in progress check and delete task. (default false)
44 | * @return boolean success
45 | */
46 | public static function remove($id = null, $force = false) {
47 | self::loadQueuetask();
48 | $retval = self::$QueueTask->remove($id, $force);
49 | QueueUtil::clearCache();
50 | return $retval;
51 | }
52 |
53 | /**
54 | * Run a task specifically.
55 | * @param string uuid
56 | * @return boolean success
57 | */
58 | public static function run($id = null) {
59 | self::loadQueueTask();
60 | return self::$QueueTask->run($id);
61 | }
62 |
63 | /**
64 | * Return the queue from QueueTask or QueueTaskLog as an associative array
65 | * @param string uuid
66 | * @return mixed array of queue or false if not found.
67 | */
68 | public static function findById($id = null) {
69 | self::loadQueueTask();
70 | if (self::$QueueTask->hasAny(array('QueueTask.id' => $id))) {
71 | return self::$QueueTask->findById($id);
72 | }
73 | self::loadQueueTaskLog();
74 | if (self::$QueueTaskLog->hasAny(array('QueueTaskLog.id' => $id))) {
75 | return self::$QueueTaskLog->findById($id);
76 | }
77 | return false;
78 | }
79 | /**
80 | * View the task as a string representation looks in QueueTask and QueueTaskLog
81 | * @param string uuid
82 | * @return string representation of task.
83 | */
84 | public static function view($id = null) {
85 | self::loadQueueTask();
86 | if (self::$QueueTask->hasAny(array('QueueTask.id' => $id))) {
87 | return self::$QueueTask->niceString($id);
88 | }
89 | self::loadQueueTaskLog();
90 | if (self::$QueueTaskLog->hasAny(array('QueueTaskLog.id' => $id))) {
91 | return self::$QueueTaskLog->niceString($id);
92 | }
93 | return false;
94 | }
95 | /**
96 | * List next X upcomming tasks.
97 | * @param int limit
98 | */
99 | public static function next($limit = 10) {
100 | self::loadQueueTask();
101 | return self::$QueueTask->next($limit, false, false);
102 | }
103 |
104 | /**
105 | * Process the Queue, runs the queue
106 | * @return boolean success
107 | */
108 | public static function process() {
109 | self::loadQueueTask();
110 | return self::$QueueTask->process();
111 | }
112 |
113 | /**
114 | * Returns the tasks in progress.
115 | * @return array of tasks currently in progress
116 | */
117 | public static function inProgress() {
118 | self::loadQueueTask();
119 | return self::$QueueTask->findInProgress();
120 | }
121 |
122 | /**
123 | * Return the in progress count
124 | * @return int in progress count.
125 | */
126 | public static function inProgressCount() {
127 | self::loadQueueTask();
128 | return self::$QueueTask->inProgressCount();
129 | }
130 |
131 | /**
132 | * Load the QueueTask Model instance
133 | */
134 | public static function loadQueueTask() {
135 | if (!self::$QueueTask) {
136 | App::uses('QueueTask','Queue.Model');
137 | self::$QueueTask = ClassRegistry::init('Queue.QueueTask');
138 | }
139 | }
140 | /**
141 | * Load the QueueTask Model instance
142 | */
143 | public static function loadQueueTaskLog() {
144 | if (!self::$QueueTaskLog) {
145 | App::uses('QueueTaskLog','Queue.Model');
146 | self::$QueueTaskLog = ClassRegistry::init('Queue.QueueTaskLog');
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/Model/QueueAppModel.php:
--------------------------------------------------------------------------------
1 | 'queued',
32 | 2 => 'in progress',
33 | 3 => 'finished',
34 | 4 => 'paused',
35 | );
36 | /**
37 | * type key to human readable
38 | * @var array
39 | * @access protected
40 | */
41 | protected $_types = array(
42 | 1 => 'model',
43 | 2 => 'shell',
44 | 3 => 'url',
45 | 4 => 'php_cmd',
46 | 5 => 'shell_cmd',
47 | );
48 | /**
49 | * afterFind will add status_human and type_human to the result
50 | * human readable and understandable type and status.
51 | * @param array of results
52 | * @param boolean primary
53 | * @return array of altered results
54 | */
55 | public function afterFind($results = array(), $primary = false){
56 | foreach ($results as $key => $val) {
57 | if (isset($val[$this->alias]['type'])) {
58 | $results[$key][$this->alias]['type_human'] = $this->_types[$val[$this->alias]['type']];
59 | }
60 | if (isset($val[$this->alias]['status'])) {
61 | $results[$key][$this->alias]['status_human'] = $this->_statuses[$val[$this->alias]['status']];
62 | }
63 | }
64 | return $results;
65 | }
66 | /**
67 | * return conditions based on searchable fields and filter
68 | *
69 | * @param string filter
70 | * @return conditions array
71 | */
72 | public function generateFilterConditions($filter = NULL, $pre = '') {
73 | $retval = array();
74 | if ($filter) {
75 | foreach ($this->searchFields as $field) {
76 | $retval['OR']["$field LIKE"] = '%' . $filter . '%';
77 | }
78 | }
79 | return $retval;
80 | }
81 | /**
82 | * This is what I want create to do, but without setting defaults.
83 | */
84 | public function clear() {
85 | $this->id = false;
86 | $this->data = array();
87 | $this->validationErrors = array();
88 | return $this->data;
89 | }
90 | /**
91 | * String to datetime stamp
92 | * @param string that is parsable by str2time
93 | * @param boolean future, force future time incriment by week.
94 | * @return date time string for MYSQL
95 | */
96 | function str2datetime($str = 'now', $future = false) {
97 | if (is_array($str) && isset($str['month']) && isset($str['day']) && isset($str['year'])) {
98 | $str = "{$str['month']}/{$str['day']}/{$str['year']}";
99 | }
100 | $format = "Y-m-d H:i:s";
101 | $retval = date($format, strtotime($str));
102 | if ($future) {
103 | $retvaltime = strtotime($retval);
104 | $time = time();
105 | $weektime = 604800; //seconds in a week
106 | while ($retvaltime < $time) {
107 | $retvaltime += $weektime;
108 | }
109 | $retval = date($format, $retvaltime);
110 | }
111 | return $retval;
112 | }
113 |
114 | /**
115 | * Returns if the variable is an int or string that matches an int
116 | * @param mixed var
117 | * @return boolean if is digit.
118 | */
119 | public function isDigit($var = null) {
120 | return (is_int($var) || (is_string($var) && preg_match('/\d+$/', $var)));
121 | }
122 |
123 | /**
124 | * Get the current record, for QueueTask or QueueTaskLog
125 | * @param string uuid
126 | * @return mixed associative array of queue or false on failure
127 | */
128 | public function findById($id = null) {
129 | if ($id) {
130 | $this->id = $id;
131 | }
132 | if (!$this->exists()) {
133 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
134 | }
135 | $retval = $this->read();
136 | return $retval[$this->alias];
137 | }
138 | /**
139 | * String representation of task
140 | * @param uuid string
141 | * @return string of task.
142 | */
143 | public function niceString($id = null) {
144 | if ($id) {
145 | $this->id = $id;
146 | }
147 | if (!$this->exists()) {
148 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
149 | }
150 | $data = $this->read();
151 | $retval = $data[$this->alias]['id'] . ' ' . $data[$this->alias]['status_human'] . ' ' . $data[$this->alias]['type_human'];
152 | $retval .= "\n\tCommand: " . $data[$this->alias]['command'];
153 | $retval .= "\n\tPriority: " . $data[$this->alias]['priority'];
154 | if ($data[$this->alias]['is_restricted']) {
155 | $retval .= "\n\tRestricted By:";
156 | if ($data[$this->alias]['scheduled'] !== null) {
157 | $retval .= "\n\t\tStart: {$data[$this->alias]['scheduled']}";
158 | if ($data[$this->alias]['scheduled_end'] !== null) {
159 | $retval .= "\n\t\tEnd: {$data[$this->alias]['scheduled_end']}";
160 | }
161 | if ($data[$this->alias]['reschedule'] !== null) {
162 | $retval .= "\n\t\tReschedule: {$data[$this->alias]['reschedule']}";
163 | }
164 | }
165 | if ($data[$this->alias]['cpu_limit'] !== null) {
166 | $retval .= "\n\t\tCPU <= {$data[$this->alias]['cpu_limit']}%";
167 | }
168 | }
169 | if ($data[$this->alias]['status'] == 3 && !empty($data[$this->alias]['executed'])) { //Finished
170 | $retval .= "\n\tExecuted on " . date('l jS \of F Y h:i:s A', strtotime($data[$this->alias]['executed'])) . '. And took ' . $data[$this->alias]['execution_time'] . ' ms.';
171 | $retval .= "\n\tResult: " . $data[$this->alias]['result'];
172 | }
173 | return $retval;
174 | }
175 | /**
176 | * Set and error and return false
177 | * @param string message
178 | * @return false
179 | * @access private
180 | */
181 | public function __errorAndExit($message) {
182 | $this->errors[$this->id][] = $message;
183 | QueueUtil::writeLog('Error: ' . $message);
184 | return false;
185 | }
186 |
187 | /**
188 | * Wrapper for getUserId, so we can mock this for testing
189 | * @return mixed result of AuthComponent::user('id');
190 | */
191 | public function _getUserId() {
192 | App::uses('AuthComponent', 'Controller/Component');
193 | return AuthComponent::user('id');
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/Config/Schema/schema.php:
--------------------------------------------------------------------------------
1 | array('type' => 'string', 'null' => false, 'default' => null, 'length' => 36, 'key' => 'primary', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
13 | 'user_id' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'user_id of who created/modified this queue. optional'),
14 | 'created' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index'),
15 | 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null),
16 | 'executed' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'datetime when executed.'),
17 | 'scheduled' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'When the task is scheduled. if null as soon as possible. Otherwise it will be first on list if it\'s the highest scheduled.'),
18 | 'scheduled_end' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'If we go past this time, don\'t execute. We need to reschedule based on reschedule.'),
19 | 'reschedule' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 50, 'collate' => 'utf8_general_ci', 'comment' => 'strtotime parsable addition to scheduled until in future if window is not null.', 'charset' => 'utf8'),
20 | 'start_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime start of execution.'),
21 | 'end_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime end of execution.'),
22 | 'cpu_limit' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 3, 'key' => 'index', 'comment' => 'percent limit of cpu to execute. (95 = less than 95% cpu usage)'),
23 | 'is_restricted' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'key' => 'index', 'comment' => 'will be 1 if hour, day, or cpu_limit are not null.'),
24 | 'priority' => array('type' => 'integer', 'null' => false, 'default' => '100', 'length' => 4, 'key' => 'index', 'comment' => 'priorty, lower the number, the higher on the list it will run.'),
25 | 'status' => array('type' => 'integer', 'null' => false, 'default' => '1', 'length' => 2, 'key' => 'index', 'comment' => '1:queued,2:inprogress,3:finished,4:paused'),
26 | 'type' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 2, 'key' => 'index', 'comment' => '1:model,2:shell,3:url,4:php_cmd,5:shell_cmd'),
27 | 'command' => array('type' => 'text', 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
28 | 'result' => array('type' => 'text', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
29 | 'indexes' => array(
30 | 'PRIMARY' => array('column' => 'id', 'unique' => 1),
31 | 'status' => array('column' => 'status', 'unique' => 0),
32 | 'type' => array('column' => 'type', 'unique' => 0),
33 | 'created' => array('column' => 'created', 'unique' => 0),
34 | 'priority' => array('column' => 'priority', 'unique' => 0),
35 | 'is_restricted' => array('column' => 'is_restricted', 'unique' => 0),
36 | 'cpu_limit' => array('column' => 'cpu_limit', 'unique' => 0),
37 | 'executed' => array('column' => 'executed', 'unique' => 0),
38 | 'scheduled' => array('column' => 'scheduled', 'unique' => 0),
39 | 'scheduled_end' => array('column' => 'scheduled_end', 'unique' => 0)
40 | ),
41 | 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB')
42 | );
43 |
44 | public $queue_tasks = array(
45 | 'id' => array('type' => 'string', 'null' => false, 'default' => null, 'length' => 36, 'key' => 'primary', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
46 | 'user_id' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'user_id of who created/modified this queue. optional'),
47 | 'created' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index'),
48 | 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null),
49 | 'executed' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'datetime when executed.'),
50 | 'scheduled' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'When the task is scheduled. if null as soon as possible. Otherwise it will be first on list if it\'s the highest scheduled.'),
51 | 'scheduled_end' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'If we go past this time, don\'t execute. We need to reschedule based on reschedule.'),
52 | 'reschedule' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 50, 'collate' => 'utf8_general_ci', 'comment' => 'strtotime parsable addition to scheduled until in future if window is not null.', 'charset' => 'utf8'),
53 | 'start_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime start of execution.'),
54 | 'end_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime end of execution.'),
55 | 'cpu_limit' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 3, 'key' => 'index', 'comment' => 'percent limit of cpu to execute. (95 = less than 95% cpu usage)'),
56 | 'is_restricted' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'key' => 'index', 'comment' => 'will be 1 if hour, day, or cpu_limit are not null.'),
57 | 'priority' => array('type' => 'integer', 'null' => false, 'default' => '100', 'length' => 4, 'key' => 'index', 'comment' => 'priorty, lower the number, the higher on the list it will run.'),
58 | 'status' => array('type' => 'integer', 'null' => false, 'default' => '1', 'length' => 2, 'key' => 'index', 'comment' => '1:queued,2:inprogress,3:finished,4:paused'),
59 | 'type' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 2, 'key' => 'index', 'comment' => '1:model,2:shell,3:url,4:php_cmd,5:shell_cmd'),
60 | 'command' => array('type' => 'text', 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
61 | 'result' => array('type' => 'text', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
62 | 'indexes' => array(
63 | 'PRIMARY' => array('column' => 'id', 'unique' => 1),
64 | 'status' => array('column' => 'status', 'unique' => 0),
65 | 'type' => array('column' => 'type', 'unique' => 0),
66 | 'created' => array('column' => 'created', 'unique' => 0),
67 | 'priority' => array('column' => 'priority', 'unique' => 0),
68 | 'is_restricted' => array('column' => 'is_restricted', 'unique' => 0),
69 | 'cpu_limit' => array('column' => 'cpu_limit', 'unique' => 0),
70 | 'executed' => array('column' => 'executed', 'unique' => 0),
71 | 'scheduled' => array('column' => 'scheduled', 'unique' => 0),
72 | 'scheduled_end' => array('column' => 'scheduled_end', 'unique' => 0)
73 | ),
74 | 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB')
75 | );
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/Test/Fixture/QueueTaskFixture.php:
--------------------------------------------------------------------------------
1 | array('type' => 'string', 'null' => false, 'default' => null, 'length' => 36, 'key' => 'primary', 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
15 | 'user_id' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'user_id of who created/modified this queue. optional'),
16 | 'created' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index'),
17 | 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null),
18 | 'executed' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'datetime when executed.'),
19 | 'scheduled' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'When the task is scheduled. if null as soon as possible. Otherwise it will be first on list if it\'s the highest scheduled.'),
20 | 'scheduled_end' => array('type' => 'datetime', 'null' => true, 'default' => null, 'key' => 'index', 'comment' => 'If we go past this time, don\'t execute. We need to reschedule based on reschedule.'),
21 | 'reschedule' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 50, 'collate' => 'utf8_general_ci', 'comment' => 'strtotime parsable addition to scheduled until in future if window is not null.', 'charset' => 'utf8'),
22 | 'start_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime start of execution.'),
23 | 'end_time' => array('type' => 'biginteger', 'null' => true, 'default' => null, 'length' => 22, 'comment' => 'microtime end of execution.'),
24 | 'cpu_limit' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 3, 'key' => 'index', 'comment' => 'percent limit of cpu to execute. (95 = less than 95% cpu usage)'),
25 | 'is_restricted' => array('type' => 'boolean', 'null' => false, 'default' => '0', 'key' => 'index', 'comment' => 'will be 1 if hour, day, or cpu_limit are not null.'),
26 | 'priority' => array('type' => 'integer', 'null' => false, 'default' => '100', 'length' => 4, 'key' => 'index', 'comment' => 'priorty, lower the number, the higher on the list it will run.'),
27 | 'status' => array('type' => 'integer', 'null' => false, 'default' => '1', 'length' => 2, 'key' => 'index', 'comment' => '1:queued,2:inprogress,3:finished,4:paused'),
28 | 'type' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 2, 'key' => 'index', 'comment' => '1:model,2:shell,3:url,4:php_cmd,5:shell_cmd'),
29 | 'command' => array('type' => 'text', 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
30 | 'result' => array('type' => 'text', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
31 | 'indexes' => array(
32 | 'PRIMARY' => array('column' => 'id', 'unique' => 1),
33 | 'status' => array('column' => 'status', 'unique' => 0),
34 | 'type' => array('column' => 'type', 'unique' => 0),
35 | 'created' => array('column' => 'created', 'unique' => 0),
36 | 'priority' => array('column' => 'priority', 'unique' => 0),
37 | 'is_restricted' => array('column' => 'is_restricted', 'unique' => 0),
38 | 'cpu_limit' => array('column' => 'cpu_limit', 'unique' => 0),
39 | 'executed' => array('column' => 'executed', 'unique' => 0),
40 | 'scheduled' => array('column' => 'scheduled', 'unique' => 0),
41 | 'scheduled_end' => array('column' => 'scheduled_end', 'unique' => 0)
42 | ),
43 | 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
44 | );
45 |
46 | /**
47 | * Records
48 | *
49 | * @var array
50 | */
51 | public $records = array(
52 | array(
53 | 'id' => '524b0c44-a3a0-4956-8428-dc3ee017215a',
54 | 'user_id' => null,
55 | 'created' => '2013-10-01 11:54:12',
56 | 'modified' => '2013-10-01 11:54:12',
57 | 'executed' => null,
58 | 'start_time' => null,
59 | 'end_time' => null,
60 | 'scheduled' => null,
61 | 'scheduled_end' => null,
62 | 'reschedule' => null,
63 | 'cpu_limit' => null,
64 | 'is_restricted' => 0,
65 | 'priority' => 100,
66 | 'status' => 1,
67 | 'type' => 1, //model
68 | 'command' => 'SomeModel::action("param","param2")',
69 | 'result' => '',
70 | ),
71 | array(
72 | 'id' => '524b0c44-a3a0-4956-8428-dc3ee017215b',
73 | 'user_id' => null,
74 | 'created' => '2013-10-01 11:54:12',
75 | 'modified' => '2013-10-01 11:54:12',
76 | 'executed' => null,
77 | 'start_time' => null,
78 | 'end_time' => null,
79 | 'scheduled' => null,
80 | 'scheduled_end' => null,
81 | 'reschedule' => null,
82 | 'cpu_limit' => null,
83 | 'is_restricted' => 0,
84 | 'priority' => 100,
85 | 'status' => 1,
86 | 'type' => 2, //shell
87 | 'command' => 'Queue.SomeShell command param1 param2',
88 | 'result' => '',
89 | ),
90 | array(
91 | 'id' => '524b0c44-a3a0-4956-8428-dc3ee017215c',
92 | 'user_id' => null,
93 | 'created' => '2013-10-01 11:54:12',
94 | 'modified' => '2013-10-01 11:54:12',
95 | 'executed' => null,
96 | 'start_time' => null,
97 | 'end_time' => null,
98 | 'scheduled' => null,
99 | 'scheduled_end' => null,
100 | 'reschedule' => null,
101 | 'cpu_limit' => null,
102 | 'is_restricted' => 0,
103 | 'priority' => 100,
104 | 'status' => 1,
105 | 'type' => 3, //shell
106 | 'command' => '/some/url/to/an/action',
107 | 'result' => '',
108 | ),
109 | array(
110 | 'id' => '524b0c44-a3a0-4956-8428-dc3ee017215d',
111 | 'user_id' => null,
112 | 'created' => '2013-10-01 11:54:12',
113 | 'modified' => '2013-10-01 11:54:12',
114 | 'executed' => null,
115 | 'start_time' => null,
116 | 'end_time' => null,
117 | 'scheduled' => null,
118 | 'scheduled_end' => null,
119 | 'reschedule' => null,
120 | 'cpu_limit' => null,
121 | 'is_restricted' => 0,
122 | 'priority' => 100,
123 | 'status' => 1,
124 | 'type' => 4, //php_command
125 | 'command' => '2 + 5',
126 | 'result' => '',
127 | ),
128 | array(
129 | 'id' => '524b0c44-a3a0-4956-8428-dc3ee017215e',
130 | 'user_id' => null,
131 | 'created' => '2013-10-01 11:54:12',
132 | 'modified' => '2013-10-01 11:54:12',
133 | 'executed' => null,
134 | 'start_time' => null,
135 | 'end_time' => null,
136 | 'priority' => 100,
137 | 'scheduled' => null,
138 | 'scheduled_end' => null,
139 | 'reschedule' => null,
140 | 'cpu_limit' => null,
141 | 'is_restricted' => 0,
142 | 'status' => 1,
143 | 'type' => 5, //shell_cmd
144 | 'command' => 'echo "hello" && echo "world"',
145 | 'result' => '',
146 | ),
147 | array(
148 | 'id' => '524b0c44-a3a0-4956-8428-dc3ee017215f',
149 | 'user_id' => null,
150 | 'created' => '2013-10-01 11:54:12',
151 | 'modified' => '2013-10-01 11:54:12',
152 | 'executed' => null,
153 | 'start_time' => null,
154 | 'end_time' => null,
155 | 'priority' => 1,
156 | 'scheduled' => null,
157 | 'scheduled_end' => null,
158 | 'reschedule' => null,
159 | 'cpu_limit' => '95',
160 | 'is_restricted' => 1,
161 | 'status' => 1,
162 | 'type' => 5, //shell_cmd
163 | 'command' => 'echo "hello" && echo "world"',
164 | 'result' => '',
165 | ),
166 | );
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/Console/Command/QueueShell.php:
--------------------------------------------------------------------------------
1 | QueueTask = ClassRegistry::init('Queue.QueueTask');
20 | }
21 |
22 | /**
23 | * {@inheritDoc}
24 | */
25 | public function getOptionParser() {
26 | $parser = parent::getOptionParser();
27 | return $parser->description(
28 | 'The Queue shell.' .
29 | '')
30 | ->addSubcommand('add', array(
31 | 'help' => __('Add a task to the queue.'),
32 | 'parser' => array(
33 | 'description' => array(
34 | __('Use this command to add tasks to the queue')
35 | ),
36 | 'arguments' => array(
37 | 'command' => array(
38 | 'help' => __('The actual command string'),
39 | 'required' => true,
40 | //'short' => 'c',
41 | )
42 | ),
43 | 'options' => array(
44 | 'type' => array(
45 | 'help' => __('The type of task command.'),
46 | 'required' => true,
47 | 'short' => 't',
48 | 'default' => 'model',
49 | 'choices' => array_values($this->QueueTask->_types)
50 | ),
51 | 'start' => array(
52 | 'help' => __('optional: strtotime parsable scheduled date to execute. \'Sunday 11 pm\',\'Tuesday 2 am\' (default null = no restriction)'),
53 | 'short' => 's',
54 | 'default' => null
55 | ),
56 | 'end' => array(
57 | 'help' => __('optional: strtotime parsable scheduled end date to execute. \'Monday 4am\' (default null = no restriction)'),
58 | 'short' => 'e',
59 | 'default' => null
60 | ),
61 | 'reschedule' => array(
62 | 'help' => __('optional: string of addition to scheduled if window of start and end are missed. Parsable by strtotime. \'+1 day\', \'+1 week\' (default null, required if end is not null)'),
63 | 'short' => 'r',
64 | 'default' => null
65 | ),
66 | 'cpu' => array(
67 | 'help' => __('optional: CPU Percent Limit to run task, 0-100. \'95\',\'10\' (default null = no restriction)'),
68 | 'short' => 'c',
69 | 'default' => null
70 | ),
71 | 'priority' => array(
72 | 'help' => __('optional: Priority of task, lower number the sooner it will run. \'5\',\'100\''),
73 | 'short' => 'p',
74 | 'default' => 100
75 | ),
76 | )
77 | )
78 | )
79 | )
80 | ->addSubcommand('run', array(
81 | 'help' => __('Run a task in the queue.'),
82 | 'parser' => array(
83 | 'description' => array(
84 | __('Use this command to run a paticular queue')
85 | ),
86 | 'arguments' => array(
87 | 'id' => array(
88 | 'help' => __('UUID of queue to run'),
89 | 'required' => true,
90 | )
91 | )
92 | )
93 | )
94 | )
95 | ->addSubcommand('view', array(
96 | 'help' => __('View a task in the queue.'),
97 | 'parser' => array(
98 | 'description' => array(
99 | __('Use this command to view a paticular queue. Including results.')
100 | ),
101 | 'arguments' => array(
102 | 'id' => array(
103 | 'help' => __('UUID of queue to view'),
104 | 'required' => true,
105 | )
106 | )
107 | )
108 | )
109 | )
110 | ->addSubcommand('next', array(
111 | 'help' => __('Show what is queued.'),
112 | 'parser' => array(
113 | 'description' => array(
114 | __('Use this command to see what X next in queue.')
115 | ),
116 | 'arguments' => array(
117 | 'limit' => array(
118 | 'help' => __('INT of how many you want to see in the future, \`10\`,\'5\''),
119 | 'required' => true,
120 | 'default' => 10
121 | )
122 | )
123 | )
124 | )
125 | )
126 | ->addSubcommand('process', array(
127 | 'help' => __('Process the queue, runs the next limit items on the queue.')
128 | )
129 | )
130 | ->addSubcommand('in_progress', array(
131 | 'help' => __('Show the queues in progress.')
132 | )
133 | )
134 | ->addSubcommand('in_progress_count', array(
135 | 'help' => __('Show the in progress count.')
136 | )
137 | )
138 | ->addSubcommand('remove', array(
139 | 'help' => __('Remove a task from the queue.'),
140 | 'parser' => array(
141 | 'description' => array(
142 | __('Use this command to remove a paticular task.')
143 | ),
144 | 'arguments' => array(
145 | 'id' => array(
146 | 'help' => __('UUID of task to remove. Will not remove in_process tasks.'),
147 | 'required' => true,
148 | )
149 | ),
150 | 'options' => array(
151 | 'force' => array(
152 | 'help' => __('if true will force a delete even on in_progress tasks.'),
153 | 'boolean' => true,
154 | 'short' => 'f',
155 | 'default' => false
156 | )
157 | )
158 | )
159 | )
160 | );
161 | }
162 |
163 | /**
164 | * Override main
165 | *
166 | * @return void
167 | */
168 | public function main() {
169 | $this->out($this->getOptionParser()->help());
170 | }
171 |
172 | public function add() {
173 | $command = array_shift($this->args);
174 | $defaults = array(
175 | 'start' => null,
176 | 'end' => null,
177 | 'reschedule' => null,
178 | 'cpu' => null,
179 | 'cpu_limit' => null,
180 | 'priority' => 100
181 | );
182 | $options = array_merge($defaults, (array) $this->params);
183 | $options['cpu_limit'] = $options['cpu'];
184 | $options = array_intersect_key($options, $defaults);
185 |
186 | if (Queue::add($command, $this->params['type'], $options)) {
187 | $this->out('Task succesfully added.', 1, Shell::QUIET);
188 | $this->out(Queue::view($this->QueueTask->id));
189 | } else {
190 | $this->out('Error adding task.');
191 | $this->out();
192 | print_r($this->QueueTask->validationErrors);
193 | }
194 | }
195 |
196 | public function remove() {
197 | $id = array_shift($this->args);
198 | $this->out('Removing ' . $id);
199 | if (Queue::remove($id, $this->params['force'])) {
200 | $this->out('Queue Removed.');
201 | } else {
202 | $this->out('Failed to remove Queue.');
203 | $this->out(Queue::view($id));
204 | }
205 | }
206 |
207 | public function view() {
208 | $id = array_shift($this->args);
209 | $this->out(Queue::view($id));
210 | }
211 |
212 | public function run() {
213 | $id = array_shift($this->args);
214 | $this->out('Running ' . $id);
215 | if (Queue::run($id)) {
216 | $this->out('Success.');
217 | $this->out(Queue::view($id));
218 | $this->out();
219 | } else {
220 | $this->out('Failed to run task. Check logs.');
221 | }
222 | }
223 |
224 | public function process() {
225 | $this->out('Processing Queue.');
226 | if (Queue::process()) {
227 | $this->out('Success.');
228 | } else {
229 | $this->out('One or more failed, Check logs.');
230 | }
231 | }
232 |
233 | public function next() {
234 | $limit = array_shift($this->args);
235 | $this->out('Retrieving Queue List.');
236 | $queue = Queue::next($limit, false);
237 | $i = 1;
238 | foreach ($queue as $task) {
239 | $this->out($i . ') ' . Queue::view($task['QueueTask']['id']));
240 | $i++;
241 | }
242 | }
243 |
244 | public function in_progress() {
245 | $this->out('Retrieving In Progress Queues.');
246 | $queue = Queue::inProgress();
247 | if (empty($queues)) {
248 | $this->out('No Tasks currently running.');
249 | exit(1);
250 | }
251 | $i = 1;
252 | foreach ($queue as $task) {
253 | $this->out($i . ') ' . Queue::view($task['QueueTask']['id']));
254 | $i++;
255 | }
256 | }
257 |
258 | public function in_progress_count() {
259 | $this->out(Queue::inProgressCount(), 1, Shell::QUIET);
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/readme.markdown:
--------------------------------------------------------------------------------
1 | # CakePHP Queue Plugin
2 |
3 | * Author: Nick Baker
4 | * Version: 1.0.0
5 | * License: MIT
6 | * Website:
7 |
8 | ## Features
9 |
10 | Complete tool to background and schedule your time consuming tasks. Queue up almost anything from CakePHP shells, models, and actions to standard php commands or even shell commands.
11 |
12 | ## Changelog
13 |
14 | * 1.0.0 Initial release
15 |
16 | ## Install
17 |
18 | Clone the repository into your `app/Plugin/Queue` directory:
19 |
20 | git clone https://github.com/webtechnick/CakePHP-Queue-Plugin.git app/Plugin/Queue
21 |
22 | Or you can install via
23 |
24 | Load the plugin in your `app/Config/bootstrap.php` file:
25 |
26 | CakePlugin::load('Queue');
27 |
28 | Run the schema into your database to install the required tables.
29 |
30 | cake schema create --plugin Queue
31 |
32 | ## Setup
33 |
34 | Create the file `app/Config/queue.php` with the defaults from `app/Plugin/Queue/Config/queue.php.default`:
35 |
36 | $config = array(
37 | 'Queue' => array(
38 | 'log' => true, //logs every task run in a log file.
39 | 'limit' => 1, //limit how many queues can run at a time. (default 1).
40 | 'allowedTypes' => array( //restrict what type of command can be queued.
41 | 1, //model
42 | 2, //shell
43 | 3, //url
44 | //4, //php_cmd
45 | //5, //shell_cmd
46 | ),
47 | 'archiveAfterExecute' => true, //will archive the task once finished executing for quicker queues
48 | 'cache' => '+5 minute', //how long to cache cpu usage and list. (false disables cache)
49 | 'cores' => '8', //number of cpu cores to correctly gauge current CPU load.
50 | )
51 | );
52 |
53 | ## Cron Setup (optional)
54 |
55 | Once you start adding things to your queue you need to process it. You can do it a few ways.
56 |
57 | 1) By setting up a cron (recommended)
58 |
59 | crontab -e
60 | */1 * * * * /path/to/app/Plugin/Queue/scripts/process_queue.sh
61 |
62 | **Note:** The cron script assumes Console/cake is executable from the root of your app directory.
63 |
64 | 2) By processing the Queue via the built in shell
65 |
66 | cake Queue.queue process
67 |
68 | 3) By processing the Queue via the built in library
69 |
70 | App::uses('Queue','Queue.Lib');
71 | Queue::process();
72 |
73 | 4) By navigating to the queue plugin admin web interface and processing the Queue (feature not available yet)
74 |
75 |
76 | ## Quick Start Guide (Shell)
77 |
78 | Add a Task to the queue.
79 |
80 | $ cake Queue.queue add "Queue.QueueTask::find('first')"
81 |
82 | Task succesfully added.
83 | 525387a1-2dd0-4100-a48f-4f4be017215a queued model
84 | Command: Queue.QueueTask::find('first')
85 | Priority: 100
86 |
87 | Process the Queue.
88 |
89 | $ cake Queue.queue process
90 |
91 | Processing Queue.
92 | Success.
93 |
94 | View the Task added.
95 |
96 | $ cake Queue.queue view 525387a1-2dd0-4100-a48f-4f4be017215a
97 |
98 | 525387a1-2dd0-4100-a48f-4f4be017215a finished model
99 | Command: Queue.QueueTask::find('first')
100 | Priority: 100
101 | Executed on Monday 7th of October 2013 10:19:10 PM. And took 0 ms.
102 | Result: {"QueueTask":{"id":"525387a1-2dd0-4100-a48f-4f4be017215a","user_id":null,"created":"2013-10-07 22:18:41","modified":"2013-10-07 22:19:10","executed":null,"scheduled":null,"scheduled_end":null,"reschedule":null,"start_time":"1381205950","end_time":null,"cpu_limit":null,"is_restricted":false,"priority":"100","status":"2","type":"1","command":"Queue.QueueTask::find('first')","result":null,"execution_time":null,"type_human":"model","status_human":"in progress"}}
103 |
104 | ## Quick Start Guide (Library)
105 |
106 | Adding a Task to the queue.
107 |
108 | App::uses('Queue', 'Queue.Lib');
109 | $task = Queue::add("Queue.QueueTask::find('first')");
110 | /* $task =
111 | 'QueueTask' => array(
112 | 'priority' => (int) 100,
113 | 'command' => 'Queue.QueueTask::find('first')',
114 | 'type' => (int) 1,
115 | 'scheduled' => null,
116 | 'scheduled_end' => null,
117 | 'reschedule' => null,
118 | 'cpu_limit' => null,
119 | 'modified' => '2013-10-07 22:22:36',
120 | 'created' => '2013-10-07 22:22:36',
121 | 'user_id' => null,
122 | 'id' => '5253888c-ae18-4ffd-991a-4436e017215a'
123 | ) */
124 |
125 | Process the queue.
126 |
127 | $result = Queue::process();
128 | /* $result will be boolean true */
129 |
130 | View the Task.
131 |
132 | $task = Queue::view('5253888c-ae18-4ffd-991a-4436e017215a');
133 | /* $task is the string representation, same as queue view. Not as useful */
134 |
135 | $task = Queue::findById('5253888c-ae18-4ffd-991a-4436e017215a');
136 | /* $task is now an associative array, much more useful.
137 | array(
138 | 'id' => '52538a36-df1c-4186-a50a-4076e017215a',
139 | 'user_id' => null,
140 | 'created' => '2013-10-07 22:29:42',
141 | 'modified' => '2013-10-07 22:29:42',
142 | 'executed' => '2013-10-07 22:29:42',
143 | 'scheduled' => null,
144 | 'scheduled_end' => null,
145 | 'reschedule' => null,
146 | 'start_time' => '1381206582',
147 | 'end_time' => '1381206582',
148 | 'cpu_limit' => null,
149 | 'is_restricted' => false,
150 | 'priority' => '100',
151 | 'status' => '3',
152 | 'type' => '1',
153 | 'command' => 'Queue.QueueTask::find('first')',
154 | 'result' => '{"QueueTask":{"id":"524b0c44-a3a0-4956-8428-dc3ee017215a","user_id":null,"created":"2013-10-01 11:54:12","modified":"2013-10-01 11:54:12","executed":null,"scheduled":null,"scheduled_end":null,"reschedule":null,"start_time":null,"end_time":null,"cpu_limit":null,"is_restricted":false,"priority":"100","status":"1","type":"1","command":"SomeModel::action(\"param\",\"param2\")","result":"","execution_time":null,"type_human":"model","status_human":"queued"}}',
155 | 'execution_time' => '0',
156 | 'type_human' => 'model',
157 | 'status_human' => 'finished'
158 | ) */
159 |
160 |
161 | ## Adding Tasks to Queue Types and Commands
162 |
163 | Once you've decided how you're going to process your queue it's time to start adding tasks to your queue.
164 |
165 | To add tasks use the built in shell or library.
166 |
167 | Queue::add($command, $type, $options = array());
168 | cake Queue.queue add "command" -t type
169 |
170 | There are *5 types* of commands you can use. The default is *model*.
171 |
172 | 1) **Model** : Model function to execute. Examples:
173 |
174 | #Command strings
175 | Model::action()
176 | Plugin.Model::action("param1","pararm2")
177 |
178 | #Adding the command to the queue.
179 | Queue::add("Plugin.Model::action('param1','pararm2')");
180 | cake Queue.queue add "Plugin.Model::action('param1','pararm2')"
181 |
182 | Models are setup via ClassRegistry::init() and methods executed on the model object as public method calls.
183 |
184 | 2) **Shell** : CakePHP shell to execute. Examples:
185 |
186 | #Command strings
187 | ClearCache.clear_cache
188 | shell_command -f flag arg1 arg2
189 |
190 | #Adding the command to the queue.
191 | Queue::add("shell_command -f flag arg1 arg2", 'shell');
192 | cake Queue.queue add "shell_command -f flag arg1 arg2" -t shell
193 |
194 | 3) **Url** : A URL to requestAction. Example:
195 |
196 | #Command string
197 | /path/to/url
198 |
199 | #Adding the command to the queue.
200 | Queue::add("/path/to/url", 'url');
201 | cake Queue.queue add "/path/to/url" -t url
202 |
203 | 4) **php_cmd** : PHP Command, a simple or complex php command. Examples:
204 |
205 | #Command strings
206 | 3 + 5
207 | mail('nick@example.com','subject','message')
208 |
209 | #Adding the command to the queue.
210 | Queue::add("mail('nick@example.com','subject','message')", 'php_cmd');
211 | cake Queue.queue add "mail('nick@example.com','subject','message')" -t php_cmd
212 |
213 | 5) **shell_cmd** : Basic Bash shell command. Examples:
214 |
215 | #Command string
216 | echo 'hello' && echo 'world'
217 |
218 | #Adding the command to the queue.
219 | Queue::add("echo 'hello' && echo 'world'", 'shell_cmd');
220 | cake Queue.queue add "echo 'hello' && echo 'world'" -t shell_cmd
221 |
222 | **NOTE** `php_cmd` and `shell_cmd` are not allowed by default. You have to turn them on in `app/Config/queue.php`.
223 |
224 | ## Viewing Your Queue
225 |
226 | You can see what is in the queue at any given time and what is up next by calling `Queue::next($limit);` or the shell
227 |
228 | cake Queue.queue next 10
229 |
230 | Retrieving Queue List.
231 | 1) 52537d35-95d0-48aa-a48d-416de017215a queued url
232 | Command: /path/to/url
233 | Priority: 100
234 | Restricted By:
235 | Start: 2013-10-11 03:00:00
236 | CPU <= 95%
237 | 2) 52537d09-6bf0-4961-94f0-4201e017215a queued model
238 | Command: Model::action()
239 | Priority: 100
240 | 3) 52535ab8-6298-4ab3-9fc6-4b49e017215a queued shell_cmd
241 | Command: echo 'hello' && echo 'world'
242 | Priority: 100
243 | Restricted By:
244 | Start: 2013-10-07 23:00:00
245 | 4) 52537d35-95d0-48aa-a48d-416de017215a queued url
246 | Command: /path/to/url
247 | Priority: 100
248 | Restricted By:
249 | Start: 2013-10-11 03:00:00
250 | CPU <= 95%
251 |
252 | You'll see a list of your upcomming queues including restrictions you set on your tasks.
253 |
254 | ## Task Restrictions
255 |
256 | There are a number of restrictions you can assign to Tasks that will modify their order in the queue and affect when the task executes.
257 | Restricted Tasks gain priority over Non-restriction tasks (basic queued tasks). The Queue process will look for restricted tasks that match
258 | the current state of the server before it looks for non-restriction tasks as non-restriction tasks are by defination not time sensitive.
259 |
260 | #### Task Options
261 |
262 | 1) **Scheduled Start** Restriction: you can schedule a task to not execute until a certain scheduled time has passed.
263 |
264 | Queue::add($command, $type, $options = array(
265 | 'start' => 'Friday 11pm',
266 | ));
267 |
268 | # Queue Shell
269 | cake Queue.queue add "command" -t type --start "Friday 11pm"
270 |
271 | 2) **Schedule End** Restriciton: you can specify an end time to make a "window" of execution time. When using this option you must also specify a reschedule option that will go into affect if the "window" is missed.
272 |
273 | Queue::add($command, $type, $options = array(
274 | 'start' => 'Friday 11pm',
275 | 'end' => 'Saturday 5am',
276 | 'reschedule' => '+1 week' //if window is missed, will add '+1 week' to 'Friday 11pm' and 'Saturday 5am'
277 | ));
278 |
279 | # Queue Shell
280 | cake Queue.queue add "command" -t type --start "Friday 11pm" --end "Saturday 5am" --reschedule "+1 week"
281 |
282 | 3) **Cpu Load** Restriction: you can specify a CPU load restriction to only execute task when CPU is below a certain threshold.
283 |
284 | # Task will execute when the current CPU load is less than 95%
285 | Queue::add($command, $type, $options = array(
286 | 'cpu' => '95',
287 | ));
288 |
289 | # Queue Shell
290 | cake Queue.queue add "command" -t type --cpu "95"
291 |
292 | 4) **Priority** Restriction: by default all Tasks added are given 100 in priority. You can change this priority when adding and will allow you to jump ahead of the line. The lower the number the closer to the top of the queue it becomes.
293 |
294 | # This will jump the task to the top of the queue (after restrictions tasks)
295 | Queue::add($command, $type, $options = array(
296 | 'priority' => '1',
297 | ));
298 |
299 | # Queue Shell
300 | cake Queue.queue add "command" -t type -p 1
301 |
302 | Mix and match your restrictions to have total control over when your tasks execute and where they're placed in the queue.
303 |
304 | ## Running A Task Manually
305 |
306 | You can bypass the queue process and explicicty run a task manually. This will bypass any restrictions execute the command and return the result.
307 |
308 | //Queue::run($id);
309 | #cake Queue.queue run
310 |
311 | $ cake Queue.queue run 52535ab8-6298-4ab3-9fc6-4b49e017215a
312 |
313 | Running 52535ab8-6298-4ab3-9fc6-4b49e017215a
314 | Success.
315 | 52535ab8-6298-4ab3-9fc6-4b49e017215a finished shell_cmd
316 | Command: echo 'hello' && echo 'world'
317 | Priority: 100
318 | Restricted By:
319 | Start: 2013-10-07 23:00:00
320 | Executed on Monday 7th of October 2013 10:06:27 PM. And took 0 ms.
321 | Result: "hello\nworld\n"
322 |
323 | ## Viewing A Task
324 |
325 | You can view any task, in the queue or in the queue log (archive after execution) at anytime.
326 | You'll see all the meta data and if it's a finished task you'll see the result of the task as well as the execution time and how long it took to complete.
327 |
328 | //$task = Queue::view($id);
329 | #cake Queue.queue view
330 |
331 | $ cake Queue.queue view 52535ab8-6298-4ab3-9fc6-4b49e017215a
332 |
333 | 52535ab8-6298-4ab3-9fc6-4b49e017215a finished shell_cmd
334 | Command: echo 'hello' && echo 'world'
335 | Priority: 100
336 | Restricted By:
337 | Start: 2013-10-07 23:00:00
338 | Executed on Monday 7th of October 2013 10:06:27 PM. And took 0 ms.
339 | Result: "hello\nworld\n"
340 |
341 | ## Viewing Tasks in Progress
342 |
343 | You can view the current tasks in progress via the library or shell.
344 |
345 | $queue = Queue::inProgress();
346 | cake Queue.queue in_progress
347 |
348 | **NOTE:** You can also get just the progress count by running `cake Queue.queue in_progress_count` or `Queue::inProgressCount();`
349 |
350 | ## Removing Tasks from Queue
351 |
352 | Remove tasks from the queue. Note this will not remove tasks that are currently in progress by default.
353 |
354 | //Queue::remove($id);
355 | cake Queue.queue remove
356 |
357 | **NOTE:** You can force a task to be removed even if it's in progress by passing `true` into `Queue::remove($id, true);` or `cake queue.queue remove -f`
358 |
359 | # Enjoy
360 |
361 | Enjoy the plugin!
362 |
--------------------------------------------------------------------------------
/Test/Case/Model/QueueTaskTest.php:
--------------------------------------------------------------------------------
1 | QueueTask = $this->getMockForModel('Queue.QueueTask', array('requestAction', 'getCurrentUser'));
37 | $this->QueueTask->Shell = $this->getMock('Shell');
38 | Configure::write('Queue.allowedTypes', array(1,2,3,4,5));
39 | }
40 |
41 | /**
42 | * tearDown method
43 | *
44 | * @return void
45 | */
46 | public function tearDown() {
47 | unset($this->QueueTask);
48 |
49 | parent::tearDown();
50 | }
51 |
52 | public function test_process() {
53 | QueueUtil::$configs['limit'] = 3;
54 | QueueUtil::$configs['archiveAfterExecute'] = true;
55 | $count = $this->QueueTask->find('count');
56 | $result = $this->QueueTask->process();
57 | $this->assertTrue($result);
58 | $this->assertEqual($this->QueueTask->find('count'), $count - 3);
59 | $QueueLog = ClassRegistry::init('Queue.QueueTaskLog');
60 | $logs = $QueueLog->find('all');
61 | $this->assertEqual(count($logs), 3);
62 | foreach ($logs as $log) {
63 | $this->assertEqual($log['QueueTaskLog']['status'], 3); //finixhed
64 | $this->assertTrue(!empty($log['QueueTaskLog']['executed']));
65 | $this->assertTrue(!empty($log['QueueTaskLog']['start_time']));
66 | $this->assertTrue(!empty($log['QueueTaskLog']['end_time']));
67 | }
68 | }
69 |
70 | public function test_removeNotInProgress() {
71 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215a';
72 | $result = $this->QueueTask->remove(); //forcing delete
73 | $this->assertTrue($result);
74 | $this->assertFalse($this->QueueTask->exists());
75 | }
76 |
77 | public function test_removeNoRemoveInProgress() {
78 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215a';
79 | $this->QueueTask->saveField('status', 2); //setting to in progress.
80 | $result = $this->QueueTask->remove();
81 | $this->assertFalse($result);
82 | $this->assertTrue($this->QueueTask->exists());
83 | }
84 |
85 | public function test_removeRemoveInProgressForce() {
86 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215a';
87 | $this->QueueTask->saveField('status', 2); //setting to in progress.
88 | $result = $this->QueueTask->remove(null, true); //forcing delete
89 | $this->assertTrue($result);
90 | $this->assertFalse($this->QueueTask->exists());
91 | }
92 |
93 | public function test_addRestricted() {
94 | //Validation rules are tested, test restricted
95 | $count = $this->QueueTask->find('count');
96 | $result = $this->QueueTask->add("Model::action()", 'model', array(
97 | 'start' => 'Sunday 11 pm',
98 | 'end' => 'Monday 5 am',
99 | 'reschedule' => '+1 week',
100 | 'cpu_limit' => 95,
101 | ));
102 | $this->assertTrue(!empty($result));
103 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
104 | $this->assertTrue($this->QueueTask->field('is_restricted'));
105 | $this->assertEqual($this->QueueTask->field('cpu_limit'), 95);
106 | $this->assertEqual($this->QueueTask->field('type'), 1);
107 | $this->assertEqual($this->QueueTask->field('priority'), 100);
108 | $this->assertTrue(!empty($result['QueueTask']['scheduled']));
109 | $this->assertTrue(!empty($result['QueueTask']['scheduled_end']));
110 | $this->assertTrue(!empty($result['QueueTask']['reschedule']));
111 | }
112 |
113 | public function test_addNormal_minimal() {
114 | $count = $this->QueueTask->find('count');
115 | $result = $this->QueueTask->add("Model::action()", 'model');
116 | $this->assertTrue(!empty($result));
117 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
118 | $this->assertFalse($this->QueueTask->field('is_restricted'));
119 | $this->assertEqual($this->QueueTask->field('scheduled'), null);
120 | $this->assertEqual($this->QueueTask->field('scheduled_end'), null);
121 | $this->assertEqual($this->QueueTask->field('reschedule'), null);
122 | $this->assertEqual($this->QueueTask->field('cpu_limit'), null);
123 | $this->assertEqual($this->QueueTask->field('type'), 1);
124 | $this->assertEqual($this->QueueTask->field('priority'), 100);
125 | }
126 |
127 | public function test_addNormal_extra() {
128 | $count = $this->QueueTask->find('count');
129 | $result = $this->QueueTask->add("Model::action()", 1, array(
130 | 'priority' => 50,
131 | ));
132 | $this->assertTrue(!empty($result));
133 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
134 | $this->assertFalse($this->QueueTask->field('is_restricted'));
135 | $this->assertEqual($this->QueueTask->field('scheduled'), null);
136 | $this->assertEqual($this->QueueTask->field('scheduled_end'), null);
137 | $this->assertEqual($this->QueueTask->field('reschedule'), null);
138 | $this->assertEqual($this->QueueTask->field('cpu_limit'), null);
139 | $this->assertEqual($this->QueueTask->field('type'), 1);
140 | $this->assertEqual($this->QueueTask->field('priority'), 50);
141 | }
142 |
143 | public function test_next() {
144 | $result = $this->QueueTask->next(2, true, false);
145 | $this->assertEqual(count($result), 2);
146 | $this->assertEqual('524b0c44-a3a0-4956-8428-dc3ee017215f', $result[0]['QueueTask']['id']);
147 | $this->assertEqual('524b0c44-a3a0-4956-8428-dc3ee017215a', $result[1]['QueueTask']['id']);
148 | }
149 |
150 | public function test_archive() {
151 | QueueUtil::$configs['archiveAfterExecute'] = false;
152 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215a';
153 | $result = $this->QueueTask->run();
154 | $this->assertTrue($result);
155 |
156 | $QueueTaskLog = ClassRegistry::init('Queue.QueueTaskLog');
157 | $count = $QueueTaskLog->find('count');
158 | $result = $this->QueueTask->archive();
159 | $this->assertFalse($this->QueueTask->exists()); //deleted
160 | $this->assertEqual($QueueTaskLog->find('count'), $count + 1);
161 | }
162 |
163 | public function test_validCommandShellCmd() {
164 | //Validate phpShell
165 | $data = array(
166 | 'QueueTask' => array(
167 | 'type' => 5,
168 | 'command' => '', //emtpy
169 | )
170 | );
171 | $count = $this->QueueTask->find('count');
172 | $result = $this->QueueTask->save($data);
173 | $this->assertFalse($result);
174 | $this->assertEqual($this->QueueTask->find('count'), $count);
175 | $this->assertTrue(!empty($this->QueueTask->validationErrors['command']));
176 | }
177 |
178 | public function test_validCommandPhpShell() {
179 | //Validate phpShell
180 | $data = array(
181 | 'QueueTask' => array(
182 | 'type' => 4,
183 | 'command' => '', //emtpy
184 | )
185 | );
186 | $count = $this->QueueTask->find('count');
187 | $result = $this->QueueTask->save($data);
188 | $this->assertFalse($result);
189 | $this->assertEqual($this->QueueTask->find('count'), $count);
190 | $this->assertTrue(!empty($this->QueueTask->validationErrors['command']));
191 |
192 | //Validate works.
193 | QueueUtil::getConfig('allowedTypes');
194 | QueueUtil::$configs['allowedTypes'] = array(1,2,3,4,5);
195 | $data = array(
196 | 'QueueTask' => array(
197 | 'type' => 4, //php cmd
198 | 'command' => '5 + 7'
199 | )
200 | );
201 | $count = $this->QueueTask->find('count');
202 | $result = $this->QueueTask->save($data);
203 | $this->assertTrue(!empty($result));
204 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
205 | $this->assertTrue(empty($this->QueueTask->validationErrors['command']));
206 | }
207 |
208 | public function test_validCommandShell() {
209 | $validationErrorCommands = array(
210 | 'cake Shell command',
211 | 'Console/cake Shell command',
212 | './cake Shell command'
213 | );
214 | foreach ($validationErrorCommands as $command) {
215 | //Validate SHell
216 | $data = array(
217 | 'QueueTask' => array(
218 | 'type' => 2, //shell
219 | 'command' => $command
220 | )
221 | );
222 | $count = $this->QueueTask->find('count');
223 | $result = $this->QueueTask->save($data);
224 | $this->assertFalse($result);
225 | $this->assertEqual($this->QueueTask->find('count'), $count);
226 | $this->assertTrue(!empty($this->QueueTask->validationErrors['command']));
227 | }
228 |
229 | //Validate works.
230 | $data = array(
231 | 'QueueTask' => array(
232 | 'type' => 2, //shell
233 | 'command' => 'Plugin.Queue command param1 param2'
234 | )
235 | );
236 | $count = $this->QueueTask->find('count');
237 | $result = $this->QueueTask->save($data);
238 | $this->assertTrue(!empty($result));
239 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
240 | $this->assertTrue(empty($this->QueueTask->validationErrors['command']));
241 | }
242 |
243 | public function test_validUrlShell() {
244 | $validationErrorCommands = array(
245 | 'someurl',
246 | );
247 | foreach ($validationErrorCommands as $command) {
248 | //Validate url
249 | $data = array(
250 | 'QueueTask' => array(
251 | 'type' => 3, //url
252 | 'command' => $command
253 | )
254 | );
255 | $count = $this->QueueTask->find('count');
256 | $result = $this->QueueTask->save($data);
257 | $this->assertFalse($result);
258 | $this->assertEqual($this->QueueTask->find('count'), $count);
259 | $this->assertTrue(!empty($this->QueueTask->validationErrors['command']));
260 | }
261 |
262 | //Validate works.
263 | $data = array(
264 | 'QueueTask' => array(
265 | 'type' => 3, //shell
266 | 'command' => '/some/url'
267 | )
268 | );
269 | $count = $this->QueueTask->find('count');
270 | $result = $this->QueueTask->save($data);
271 | $this->assertTrue(!empty($result));
272 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
273 | $this->assertTrue(empty($this->QueueTask->validationErrors['command']));
274 | }
275 |
276 | public function test_validCommandModel() {
277 | $data = array(
278 | 'QueueTask' => array(
279 | //'type' => 1, //no type, validation error
280 | 'command' => 'Model::action()',
281 | )
282 | );
283 | $count = $this->QueueTask->find('count');
284 | $result = $this->QueueTask->save($data);
285 | $this->assertFalse($result);
286 | $this->assertEqual($this->QueueTask->find('count'), $count);
287 | $this->assertTrue(!empty($this->QueueTask->validationErrors['type']));
288 |
289 | //Validate Model
290 | $validationErrorCommands = array(
291 | 'Model:action()',
292 | 'Model::action(',
293 | );
294 | foreach ($validationErrorCommands as $command) {
295 | //Validate model
296 | $data = array(
297 | 'QueueTask' => array(
298 | 'type' => 1, //model
299 | 'command' => $command
300 | )
301 | );
302 | $count = $this->QueueTask->find('count');
303 | $result = $this->QueueTask->save($data);
304 | $this->assertFalse($result);
305 | $this->assertEqual($this->QueueTask->find('count'), $count);
306 | $this->assertTrue(!empty($this->QueueTask->validationErrors['command']));
307 | }
308 |
309 | //Validate works.
310 | $data = array(
311 | 'QueueTask' => array(
312 | 'type' => 1, //model
313 | 'command' => 'Model::action("param2")'
314 | )
315 | );
316 | $count = $this->QueueTask->find('count');
317 | $result = $this->QueueTask->save($data);
318 | $this->assertTrue(!empty($result));
319 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
320 | $this->assertTrue(empty($this->QueueTask->validationErrors['command']));
321 | }
322 |
323 | public function test_typeValidate(){
324 | $data = array(
325 | 'QueueTask' => array(
326 | 'type' => 99, //no exist
327 | 'command' => 'Model::action()',
328 | )
329 | );
330 | $count = $this->QueueTask->find('count');
331 | $result = $this->QueueTask->save($data);
332 | $this->assertFalse($result);
333 | $this->assertEqual($this->QueueTask->find('count'), $count);
334 | $this->assertTrue(!empty($this->QueueTask->validationErrors['type']));
335 | }
336 |
337 | public function test_typeStatus(){
338 | $data = array(
339 | 'QueueTask' => array(
340 | 'type' => 1, //model
341 | 'command' => 'Model::action()',
342 | 'status' => 99, //no exist
343 | )
344 | );
345 | $count = $this->QueueTask->find('count');
346 | $result = $this->QueueTask->save($data);
347 | $this->assertFalse($result);
348 | $this->assertEqual($this->QueueTask->find('count'), $count);
349 | $this->assertTrue(!empty($this->QueueTask->validationErrors['status']));
350 | }
351 |
352 | public function test_typeValidateAllowed(){
353 | QueueUtil::getConfig('allowedTypes');
354 | QueueUtil::$configs['allowedTypes'] = array(1,2);
355 | $data = array(
356 | 'QueueTask' => array(
357 | 'type' => 3, //valid type but not allowed
358 | 'command' => '/url/to/queue',
359 | )
360 | );
361 | $count = $this->QueueTask->find('count');
362 | $result = $this->QueueTask->save($data);
363 | $this->assertFalse($result);
364 | $this->assertEqual($this->QueueTask->find('count'), $count);
365 | $this->assertTrue(!empty($this->QueueTask->validationErrors['type']));
366 |
367 | //But a valid type works
368 | $data = array(
369 | 'QueueTask' => array(
370 | 'type' => 1, //valid
371 | 'command' => 'Model::action()',
372 | )
373 | );
374 | $result = $this->QueueTask->save($data);
375 | $this->assertTrue(!empty($result));
376 | $this->assertEqual($this->QueueTask->find('count'), $count + 1);
377 | $this->assertTrue(empty($this->QueueTask->validationErrors['type']));
378 | }
379 |
380 | public function test_saveUser(){
381 | /*
382 | BROKEN, I'm not sure why I've set the mock correctly
383 | but AuthComponent::user is still being called. TODO
384 |
385 | $this->QueueTask->expects($this->once())
386 | ->method('getCurrentUser')
387 | ->with('id')
388 | ->will($this->returnValue('1'));
389 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215a';
390 | $this->assertEqual($this->QueueTask->field('user_id'), null);
391 | $result = $this->QueueTask->saveField('type', 1);
392 | $this->assertTrue(!empty($result));
393 | $this->assertEqual($this->QueueTask->field('user_id'), 1);
394 | */
395 | }
396 |
397 | public function test_afterFind(){
398 | $result = $this->QueueTask->find('first');
399 | $this->assertEqual($result['QueueTask']['type_human'], 'model');
400 | $this->assertEqual($result['QueueTask']['status_human'], 'queued');
401 | $this->assertEqual($result['QueueTask']['execution_time'], 0);
402 | }
403 |
404 | public function test_runModel(){
405 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215a';
406 | $executed = $this->QueueTask->field('executed');
407 | $this->assertTrue(empty($executed));
408 |
409 | $result = $this->QueueTask->run();
410 | $this->assertTrue($result);
411 | $this->assertEqual($this->QueueTask->field('result'), '["param","param2"]');
412 | $this->assertEqual($this->QueueTask->field('status'), 3);
413 | $executed = $this->QueueTask->field('executed');
414 | $this->assertTrue(!empty($executed));
415 | $start_time = $this->QueueTask->field('start_time');
416 | $this->assertTrue(!empty($start_time));
417 | $end_time = $this->QueueTask->field('end_time');
418 | $this->assertTrue(!empty($end_time));
419 | }
420 |
421 | public function test_runShell(){
422 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215b';
423 | $executed = $this->QueueTask->field('executed');
424 | $this->assertTrue(empty($executed));
425 |
426 | $this->QueueTask->Shell->expects($this->once())
427 | ->method('dispatchShell')
428 | ->with('Queue.SomeShell command param1 param2')
429 | ->will($this->returnValue('command executed.'));
430 | $result = $this->QueueTask->run();
431 |
432 | $this->assertTrue($result);
433 | $this->assertEqual($this->QueueTask->field('result'), '"command executed."');
434 | $this->assertEqual($this->QueueTask->field('status'), 3);
435 | $executed = $this->QueueTask->field('executed');
436 | $this->assertTrue(!empty($executed));
437 | $start_time = $this->QueueTask->field('start_time');
438 | $this->assertTrue(!empty($start_time));
439 | $end_time = $this->QueueTask->field('end_time');
440 | $this->assertTrue(!empty($end_time));
441 | }
442 |
443 | public function test_runUrl(){
444 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215c';
445 | $executed = $this->QueueTask->field('executed');
446 | $this->assertTrue(empty($executed));
447 |
448 | $this->QueueTask->expects($this->once())
449 | ->method('requestAction')
450 | ->with('/some/url/to/an/action')
451 | ->will($this->returnValue('hi '));
452 | $result = $this->QueueTask->run();
453 |
454 | $this->assertTrue($result);
455 | $this->assertEqual($this->QueueTask->field('result'), '"hi<\/title><\/head><\/html>"');
456 | $this->assertEqual($this->QueueTask->field('status'), 3);
457 | $executed = $this->QueueTask->field('executed');
458 | $this->assertTrue(!empty($executed));
459 | $start_time = $this->QueueTask->field('start_time');
460 | $this->assertTrue(!empty($start_time));
461 | $end_time = $this->QueueTask->field('end_time');
462 | $this->assertTrue(!empty($end_time));
463 | }
464 |
465 | public function test_runPhpCmd(){
466 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215d';
467 | $executed = $this->QueueTask->field('executed');
468 | $this->assertTrue(empty($executed));
469 |
470 | $result = $this->QueueTask->run();
471 |
472 | $this->assertTrue($result);
473 | $this->assertEqual($this->QueueTask->field('result'), '7');
474 | $this->assertEqual($this->QueueTask->field('status'), 3);
475 | $executed = $this->QueueTask->field('executed');
476 | $this->assertTrue(!empty($executed));
477 | $start_time = $this->QueueTask->field('start_time');
478 | $this->assertTrue(!empty($start_time));
479 | $end_time = $this->QueueTask->field('end_time');
480 | $this->assertTrue(!empty($end_time));
481 | }
482 |
483 | public function test_runShellCmd(){
484 | $this->QueueTask->id = '524b0c44-a3a0-4956-8428-dc3ee017215e';
485 | $executed = $this->QueueTask->field('executed');
486 | $this->assertTrue(empty($executed));
487 |
488 | $result = $this->QueueTask->run();
489 |
490 | $this->assertTrue($result);
491 | $this->assertEqual($this->QueueTask->field('result'), '"hello\nworld\n"');
492 | $this->assertEqual($this->QueueTask->field('status'), 3);
493 | $executed = $this->QueueTask->field('executed');
494 | $this->assertTrue(!empty($executed));
495 | $start_time = $this->QueueTask->field('start_time');
496 | $this->assertTrue(!empty($start_time));
497 | $end_time = $this->QueueTask->field('end_time');
498 | $this->assertTrue(!empty($end_time));
499 | }
500 |
501 | public function test_runNoShell(){
502 | $this->QueueTask->id = 'invalid_id';
503 | $result = $this->QueueTask->run();
504 | $this->assertFalse($result);
505 | $this->assertEqual('QueueTask invalid_id not found.', $this->QueueTask->errors['invalid_id'][0]);
506 | }
507 |
508 | }
509 |
--------------------------------------------------------------------------------
/Model/QueueTask.php:
--------------------------------------------------------------------------------
1 | array(
37 | 'validStatus' => array(
38 | 'rule' => array('validStatus'),
39 | 'message' => 'Please select a valid status',
40 | ),
41 | ),
42 | 'type' => array(
43 | 'validType' => array(
44 | 'rule' => array('validType'),
45 | 'message' => 'Please select a valid type',
46 | ),
47 | 'allowedType' => array(
48 | 'rule' => array('allowedType'),
49 | 'message' => 'Specified Type is not allowed by your configuration file. check Config/queue.php'
50 | )
51 | ),
52 | 'scheduled' => array(
53 | 'datetime' => array(
54 | 'rule' => array('datetime'),
55 | 'message' => 'Must be a valid date time stamp.',
56 | 'allowEmpty' => true
57 | )
58 | ),
59 | 'scheduled_end' => array(
60 | 'validWindow' => array(
61 | 'rule' => array('datetime'),
62 | 'message' => 'Must be a valid datetime stamp, or null.',
63 | 'allowEmpty' => true
64 | )
65 | ),
66 | 'reschedule' => array(
67 | 'validReschedule' => array(
68 | 'rule' => array('validReschedule'),
69 | 'message' => 'Cannot be empty when schedule_end is not null. Must be a strtotime parsable string.',
70 | )
71 | ),
72 | 'cpu_limit' => array(
73 | 'validCpu' => array(
74 | 'rule' => array('range', -1, 101),
75 | 'message' => 'Cpu Percent Limit must be between 0 and 100.',
76 | 'allowEmpty' => true
77 | )
78 | ),
79 | 'is_restricted' => array(
80 | 'boolean' => array(
81 | 'rule' => array('boolean'),
82 | 'message' => 'is_required must be a 0 or 1'
83 | )
84 | ),
85 | 'command' => array(
86 | 'notBlank' => array(
87 | 'rule' => array('notBlank'),
88 | 'message' => 'No command. Please specify.',
89 | ),
90 | 'validCommand' => array(
91 | 'rule' => array('validCommand'),
92 | 'message' => 'Command not valid for type.'
93 | )
94 | ),
95 | );
96 |
97 | public $virtualFields = array(
98 | 'execution_time' => 'QueueTask.end_time - QueueTask.start_time'
99 | );
100 |
101 | /**
102 | * Filter fields
103 | * @var array
104 | */
105 | public $searchFields = array(
106 | 'QueueTask.command','QueueTask.id','QueueTask.status','QueueTask.type'
107 | );
108 | /**
109 | * Placeholder for shell
110 | */
111 | public $Shell = null;
112 |
113 | /**
114 | * Construct to load config setting if we have cache
115 | */
116 | public function __construct($id = false, $table = null, $ds = null) {
117 | if (QueueUtil::getConfig('cache')) {
118 | QueueUtil::configCache();
119 | }
120 | return parent::__construct($id, $table, $ds);
121 | }
122 | /**
123 | * Assign the user_id if we have one.
124 | * @param array options
125 | * @return boolean success
126 | */
127 | public function beforeSave($options = array()) {
128 | $user_id_method = QueueUtil::getConfig('userIdMethod');
129 | if (!empty($user_id_method)) {
130 | if ($user_id = $this->$user_id_method()) {
131 | $this->data[$this->alias]['user_id'] = $user_id;
132 | }
133 | }
134 | return parent::beforeSave($options);
135 | }
136 | /**
137 | * Validataion of Type
138 | * @param array field
139 | * @return boolean if valid
140 | */
141 | public function validType($field) {
142 | return isset($this->_types[$field['type']]);
143 | }
144 |
145 | /**
146 | * Validataion of Status
147 | * @param array field
148 | * @return boolean if valid
149 | */
150 | public function validStatus($field) {
151 | return isset($this->_statuses[$field['status']]);
152 | }
153 |
154 | /**
155 | * Cannot be null unless scheduled_end is also null
156 | * @param arry field
157 | * @return boolean if valid
158 | */
159 | public function validReschedule($field) {
160 | if (isset($this->data[$this->alias]['scheduled_end']) && !empty($this->data[$this->alias]['scheduled_end']) && empty($field['reschedule'])) {
161 | return false;
162 | }
163 | return true;
164 | }
165 | /**
166 | * Validataion of Command
167 | * @param array field
168 | * @return boolean if valid
169 | */
170 | public function validCommand($field) {
171 | if (!isset($this->data[$this->alias]['type'])) {
172 | $this->invalidate('type', 'type must be present to validate command');
173 | return false;
174 | }
175 | switch ($this->data[$this->alias]['type']) {
176 | case 1: //Model must have :: and ) as last character.
177 | if (strpos($field['command'], '::') === false || substr($field['command'], -1) != ')') {
178 | $this->invalidate('command', 'Please use Model Syntax: \'SomeModel::action()\' \'Plugin.SomeModel::action("param","param")\'');
179 | return false;
180 | }
181 | break;
182 | case 2: //Shell must not have whole word 'cake' in front.
183 | $nostrings = array('cake','./cake','Console/cake');
184 | foreach ($nostrings as $string) {
185 | if (strpos($field['command'], $string) === 0) {
186 | $this->invalidate('command', 'Specify shell commands as though using dispatchShell string: \'Plugin.SomeShell command param1 param2\'');
187 | return false;
188 | }
189 | }
190 | break;
191 | case 3: //url must have a / in it
192 | if (strpos($field['command'], '/') === false) {
193 | $this->invalidate('command', 'Url must contain a /: \'/path/to/action\' \'http://example.com/path/to/action\'');
194 | return false;
195 | }
196 | break;
197 | case 4: //php_command basically can't be empty.
198 | if (empty($field['command'])) {
199 | $this->invalidate('command', 'PhpCmd must not be empty: \'5 + 7\'');
200 | return false;
201 | }
202 | break;
203 | case 5: //shell command basically can't be empty.
204 | if (empty($field['command'])) {
205 | $this->invalidate('command', 'ShellCmd must not be empty: \'echo "hello" && echo "world"\'');
206 | return false;
207 | }
208 | break;
209 | default: //we shouldn't get here, something went really wrong if we did but definately don't want to return true if we do.
210 | $this->invalidate('command', 'Unknown Type, cannot validate command');
211 | return false;
212 | }
213 | return true;
214 | }
215 |
216 | /**
217 | * Validataion of Type Allowed, based on configuration app/Config/queue.php
218 | * @param array field
219 | * @return boolean if valid
220 | */
221 | public function allowedType($field) {
222 | $allowedTypes = QueueUtil::getConfig('allowedTypes');
223 | return in_array($field['type'], $allowedTypes);
224 | }
225 | /**
226 | * This converts the admin save to
227 | */
228 | public function adminSave($data = array()) {
229 | $options = $data['QueueTask'];
230 | $command = $data['QueueTask']['command'];
231 | $type = $data['QueueTask']['type'];
232 | return $this->add($command, $type, $options);
233 | }
234 | /**
235 | * Convience function utilized by Queue::add() library
236 | * @param string command
237 | * @param string type
238 | * @param array of options
239 | * - start = strtotime datetime to execute. Will assume future date. (11 pm Sunday) (default null)
240 | * - end = strtotime datetime of window allowed to execute (5 am Monday) (default null)
241 | * - reschedule = strtotime addition to scheduled to execute. (+1 day | +1 week) (default null)
242 | * - cpu_limit = int 0-100 percent threshold for when to execute (95 will execute will less than 95% cpu load (default null).
243 | * if left null, as soon as possible will be assumed.
244 | * - priority = the priority of the task, a way to Cut in line. (default 100)
245 | * - id = if provided, will update the record instead of creating a new one.
246 | * @return boolean success
247 | */
248 | public function add($command, $type, $options = array()) {
249 | if (!$command || !$type) {
250 | return $this->__errorAndExit("Command and Type required to add Task to Queue.");
251 | }
252 | $options = array_merge(array(
253 | 'id' => null,
254 | 'start' => null,
255 | 'end' => null,
256 | 'reschedule' => null,
257 | 'cpu_limit' => null,
258 | 'cpu' => null,
259 | 'priority' => 100,
260 | 'scheduled' => null,
261 | 'scheduled_end' => null,
262 | ), (array) $options);
263 | $options['cpu_limit'] = $options['cpu'];
264 |
265 | if (!$this->isDigit($type)) {
266 | $type = $this->__findType($type);
267 | }
268 |
269 | if ($options['start'] !== null) {
270 | $options['scheduled'] = $this->str2datetime($options['start']);
271 | }
272 |
273 | if ($options['end'] !== null) {
274 | $options['scheduled_end'] = $this->str2datetime($options['end'], true);
275 | }
276 |
277 | $data = array(
278 | 'id' => $options['id'],
279 | 'priority' => $options['priority'],
280 | 'command' => $command,
281 | 'type' => $type,
282 | 'scheduled' => $options['scheduled'],
283 | 'scheduled_end' => $options['scheduled_end'],
284 | 'reschedule' => $options['reschedule'],
285 | 'cpu_limit' => $options['cpu_limit']
286 | );
287 | if (!empty($options['scheduled']) || !empty($options['cpu_limit'])) {
288 | $data['is_restricted'] = true;
289 | }
290 | $this->clear();
291 | return $this->save($data);
292 | }
293 |
294 | /**
295 | * Remove is a wrapper for delete that will check in progress status before
296 | * removing.
297 | * @param string uuid
298 | * @param boolean force - if true will bypass in progress check and delete task. (default false)
299 | * @throws Exception.
300 | * @return boolean
301 | */
302 | public function remove($id = null, $force = false) {
303 | if ($id) {
304 | $this->id = $id;
305 | }
306 | if (!$this->exists()) {
307 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
308 | }
309 | if (!$force && $this->field('status') == 2) { //In progress
310 | return $this->__errorAndExit("QueueTask {$this->id} is currently in progress.");
311 | }
312 | return $this->delete($id);
313 | }
314 | /**
315 | * Return count of in progress queues
316 | * @return int number of running queues.
317 | */
318 | public function inProgressCount() {
319 | return $this->find('count', array(
320 | 'conditions' => array(
321 | "{$this->alias}.status" => 2 //in_progress
322 | )
323 | ));
324 | }
325 | /**
326 | * Find for view. Will search through this
327 | * table and queueTaskLog as well
328 | */
329 | public function findForView($id = null) {
330 | $retval = $this->find('first', array(
331 | 'conditions' => array(
332 | 'QueueTask.id' => $id
333 | )
334 | ));
335 | if (empty($retval)) {
336 | $log = ClassRegistry::init('Queue.QueueTaskLog')->find('first', array(
337 | 'conditions' => array(
338 | 'QueueTaskLog.id' => $id
339 | )
340 | ));
341 | if (!empty($log)) {
342 | $retval = array(
343 | 'QueueTask' => $log['QueueTaskLog']
344 | );
345 | }
346 | }
347 | return $retval;
348 | }
349 | /**
350 | * Generate the list of next 10 in queue.
351 | * @param int limit of how many to return for next in queue
352 | * @param boolean minimal fields returned
353 | * @param processing, if true only return true set of what needs to be executed next NOW
354 | * @return array of tasks in order of execution next.
355 | */
356 | public function next($limit = 10, $minimal = true, $processing = true) {
357 | //If we don't have any queued in table just exit with empty set.
358 | if (!$this->hasAny(array("{$this->alias}.status" => 1))) {
359 | return array();
360 | }
361 | $cpu = QueueUtil::currentCpu();
362 | $now = $this->str2datetime();
363 | $fields = $minimal ? array("{$this->alias}.id") : array("{$this->alias}.*");
364 | //Set of conditions in order
365 | $conditions = array(
366 | array( //Look for restricted by scheduled and with a window with cpu
367 | "{$this->alias}.is_restricted" => true,
368 | "{$this->alias}.status" => 1,
369 | "{$this->alias}.scheduled <=" => $now,
370 | 'OR' => array(
371 | array("{$this->alias}.scheduled_end >=" => $now),
372 | array("{$this->alias}.scheduled_end" => null),
373 | ),
374 | "{$this->alias}.cpu_limit >=" => $cpu,
375 | ),
376 | array( //Look for restricted by scheduled and with a window
377 | "{$this->alias}.is_restricted" => true,
378 | "{$this->alias}.status" => 1,
379 | "{$this->alias}.scheduled <=" => $now,
380 | 'OR' => array(
381 | array("{$this->alias}.scheduled_end >=" => $now),
382 | array("{$this->alias}.scheduled_end" => null),
383 | ),
384 | ),
385 | array( //Look for restricted by cpu
386 | "{$this->alias}.is_restricted" => true,
387 | "{$this->alias}.status" => 1,
388 | "{$this->alias}.cpu_limit >=" => $cpu,
389 | ),
390 | array( //Unrestricted
391 | "{$this->alias}.is_restricted" => false,
392 | "{$this->alias}.status" => 1,
393 | )
394 | );
395 |
396 | if (!$processing) {
397 | $conditions[] = array( //Future scheduled
398 | "{$this->alias}.is_restricted" => true,
399 | "{$this->alias}.status" => 1,
400 | "{$this->alias}.scheduled >=" => $now,
401 | );
402 | }
403 |
404 | $retval = array();
405 | foreach ($conditions as $condition) {
406 | $current_count = count($retval);
407 | if ($current_count >= $limit) {
408 | break;
409 | }
410 | $new_limit = $limit - $current_count;
411 | $result = $this->find('all', array(
412 | 'limit' => $new_limit,
413 | 'order' => array("{$this->alias}.scheduled ASC, {$this->alias}.priority ASC"),
414 | 'fields' => $fields,
415 | 'conditions' => $condition
416 | ));
417 | if (!empty($result)) {
418 | $retval = array_merge($retval, $result);
419 | }
420 | }
421 | return $retval;
422 | }
423 |
424 | /**
425 | * Returns a list to run. Processing list
426 | * @return array set of queues to run.
427 | */
428 | public function runList($minimal = true) {
429 | $limit = QueueUtil::getConfig('limit');
430 | $in_progress = $this->inProgressCount();
431 | //If we have them in progress shortcut it.
432 | if ($in_progress >= $limit) {
433 | return array();
434 | }
435 | return $this->next($limit - $in_progress, $minimal, true);
436 | }
437 |
438 | /**
439 | * Actually run the queue
440 | * @param string uuid
441 | * @return boolean success
442 | */
443 | public function run($id = null) {
444 | if ($id) {
445 | $this->id = $id;
446 | }
447 | if (!$this->exists()) {
448 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
449 | }
450 | $this->__setInProgress($id);
451 | $data = $this->read();
452 | QueueUtil::writeLog('Running Queue ID: ' . $id);
453 | switch ($data[$this->alias]['type']) {
454 | case 1:
455 | $retval = $this->__runModelQueue($data);
456 | break;
457 | case 2:
458 | $retval = $this->__runShellQueue($data);
459 | break;
460 | case 3:
461 | $retval = $this->__runUrlQueue($data);
462 | break;
463 | case 4:
464 | $retval = $this->__runPhpCmdQueue($data);
465 | break;
466 | case 5:
467 | $retval = $this->__runShellCmdQueue($data);
468 | break;
469 | default:
470 | $this->__setToPaused($id);
471 | throw new Exception("Unknown Type");
472 | }
473 | $this->__setFinished($id, $retval['result']);
474 | return $retval['success'];
475 | }
476 |
477 | /**
478 | * Process the queue, this is the entry point of the shell and cron
479 | * @return boolean success
480 | */
481 | public function process() {
482 | $queues = $this->runList();
483 | if (empty($queues)) {
484 | return true;
485 | }
486 | $retval = true;
487 | foreach ($queues as $queue) {
488 | if (!$this->run($queue[$this->alias]['id'])) {
489 | $retval = false;
490 | }
491 | }
492 | return $retval;
493 | }
494 |
495 | /**
496 | * Archive this current QueueTask into QueueTaskLogs table
497 | * @param string uuid id
498 | * @return boolean success
499 | */
500 | public function archive($id = null) {
501 | if ($id) {
502 | $this->id = $id;
503 | }
504 | if (!$this->exists()) {
505 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
506 | }
507 | $data = $this->read();
508 | if ($data[$this->alias]['status'] != 3) { //Finished
509 | return false;
510 | }
511 | if (!ClassRegistry::init('Queue.QueueTaskLog')->save($data['QueueTask'])) {
512 | return false;
513 | }
514 | return $this->delete($this->id);
515 | }
516 | /**
517 | * Generate filter conditions for filter search
518 | * @param string filter
519 | * @param string pre character for search (default '') optional '%'
520 | */
521 | public function generateFilterConditions($filter = null, $pre = '') {
522 | $conditions = parent::generateFilterConditions($filter, $pre);
523 | foreach ($this->_statuses as $key => $name) {
524 | if (strtolower($filter) == $name) {
525 | $conditions['OR']["{$this->alias}.status"] = $key;
526 | unset($conditions['OR']["{$this->alias}.status LIKE"]);
527 | }
528 | }
529 | foreach ($this->_types as $key => $name) {
530 | if (strtolower($filter) == $name) {
531 | $conditions['OR']["{$this->alias}.type"] = $key;
532 | unset($conditions['OR']["{$this->alias}.type LIKE"]);
533 | }
534 | }
535 | }
536 |
537 | /**
538 | * Find tasks that are currently in progress
539 | * @return array of tasks in progress
540 | */
541 | public function findInProgress() {
542 | return $this->find('all', array(
543 | 'conditions' => array(
544 | "{$this->alias}.status" => 2 //in progress
545 | )
546 | ));
547 | }
548 |
549 | /**
550 | * Set and error and return false
551 | * @param string message
552 | * @return false
553 | * @access private
554 | */
555 | private function __clearErrors() {
556 | $this->errors = array();
557 | }
558 |
559 | /**
560 | * run the actual model command
561 | * @param queue data
562 | * @return array of result and success
563 | * @access private
564 | */
565 | private function __runModelQueue($data) {
566 | $retval = array(
567 | 'success' => false,
568 | 'result' => null
569 | );
570 | if (isset($data[$this->alias]['command'])) {
571 | list($pluginmodel, $function) = explode('::',$data[$this->alias]['command'], 2);
572 | list($plugin, $model) = pluginSplit($pluginmodel);
573 | if (!empty($plugin)) {
574 | App::uses($model, "$plugin.Model");
575 | } else {
576 | App::uses($model, "Model");
577 | }
578 | $Model = ClassRegistry::init($pluginmodel);
579 | $command = "\$retval['result'] = \$Model->$function;";
580 | @eval($command);
581 | if ($retval['result'] !== false) {
582 | $retval['success'] = true;
583 | }
584 | }
585 | return $retval;
586 | }
587 |
588 | /**
589 | * run the actual shell command
590 | * @param queue data
591 | * @return array of result and success
592 | * @access private
593 | */
594 | private function __runShellQueue($data) {
595 | $retval = array(
596 | 'success' => false,
597 | 'result' => null
598 | );
599 | if (isset($data[$this->alias]['command'])) {
600 | if (!$this->Shell) {
601 | App::uses('Shell','Console');
602 | App::uses('ShellDispatcher','Console');
603 | $this->Shell = new Shell();
604 | }
605 | $retval['result'] = $this->Shell->dispatchShell($data[$this->alias]['command']);
606 | if ($retval['result'] !== false) {
607 | $retval['success'] = true;
608 | }
609 | }
610 | return $retval;
611 | }
612 | /**
613 | * run the actual url command
614 | * @param queue data
615 | * @return array of result and success
616 | * @access private
617 | */
618 | private function __runUrlQueue($data) {
619 | $retval = array(
620 | 'success' => false,
621 | 'result' => null
622 | );
623 | if (isset($data[$this->alias]['command'])) {
624 | $retval['result'] = $this->requestAction($data[$this->alias]['command'], array('return' => true));
625 | if ($retval['result'] !== false) {
626 | $retval['success'] = true;
627 | }
628 | }
629 | return $retval;
630 | }
631 | /**
632 | * run the actual php_cmd command
633 | * @param queue data
634 | * @return array of result and success
635 | * @access private
636 | */
637 | private function __runPhpCmdQueue($data) {
638 | $retval = array(
639 | 'success' => false,
640 | 'result' => null
641 | );
642 | if (isset($data[$this->alias]['command'])) {
643 | $cmd = $data[$this->alias]['command'];
644 | $command = "\$retval['result'] = $cmd;";
645 | @eval($command);
646 | if ($retval['result'] !== false) {
647 | $retval['success'] = true;
648 | }
649 | }
650 | return $retval;
651 | }
652 | /**
653 | * run the actual shell_cmd command
654 | * @param queue data
655 | * @return array of result and success
656 | * @access private
657 | */
658 | private function __runShellCmdQueue($data) {
659 | $retval = array(
660 | 'success' => false,
661 | 'result' => null
662 | );
663 | if (isset($data[$this->alias]['command'])) {
664 | $retval['result'] = shell_exec($data[$this->alias]['command']);
665 | if ($retval['result'] !== false) {
666 | $retval['success'] = true;
667 | }
668 | }
669 | return $retval;
670 | }
671 |
672 | /**
673 | * Set the current queue to inprogress
674 | * @param string id (optional)
675 | * @return boolean success
676 | */
677 | private function __setInProgress($id = null) {
678 | if ($id) {
679 | $this->id = $id;
680 | }
681 | if (!$this->exists()) {
682 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
683 | }
684 | QueueUtil::writeLog('Starting Execution on Task: ' . $this->id);
685 | $this->saveField('start_time', microtime(true));
686 | return $this->saveField('status', 2);
687 | }
688 |
689 | /**
690 | * Set the current queue to paused
691 | * @param string id (optional)
692 | * @return boolean success
693 | */
694 | private function __setToPaused($id = null) {
695 | if ($id) {
696 | $this->id = $id;
697 | }
698 | if (!$this->exists()) {
699 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
700 | }
701 | QueueUtil::writeLog('Pausing Task: ' . $this->id);
702 | $this->saveField('end_time', microtime(true));
703 | return $this->saveField('status', 4);
704 | }
705 |
706 | /**
707 | * Set the current queue to finished
708 | * Will archive the task after execution
709 | * @param string id (optional)
710 | * @param mixed result to save back
711 | * @return boolean success
712 | */
713 | private function __setFinished($id = null, $result = null) {
714 | if ($id) {
715 | $this->id = $id;
716 | }
717 | if (!$this->exists()) {
718 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
719 | }
720 | $save_result = json_encode($result);
721 | $this->saveField('status', 3);
722 | $this->saveField('result', $save_result);
723 | $this->saveField('end_time', microtime(true));
724 | $this->saveField('executed',$this->str2datetime());
725 | QueueUtil::writeLog('Finished Execution on Task: ' . $this->id . "\nTook: " . $this->field('execution_time') . "\nResult:\n\n" . $save_result);
726 | if (QueueUtil::getConfig('archiveAfterExecute')) {
727 | $this->archive($this->id);
728 | }
729 | return true;
730 | }
731 |
732 | /**
733 | * Find the type int by a string
734 | * @param string type (model, shell, php_cmd, etc...)
735 | * @return mixed int of correct type or false if invalid.
736 | */
737 | private function __findType($stringType) {
738 | $stringType = strtolower($stringType);
739 | $type = array_search($stringType, $this->_types);
740 | if ($type !== false) {
741 | return $type;
742 | }
743 | return false;
744 | }
745 |
746 | /**
747 | * Find the status int by a string
748 | * @param string status (queued, in_progress, etc..)
749 | * @return mixed int of correct status or false if invalid.
750 | */
751 | private function __findStatus($stringStatus) {
752 | $stringStatus = strtolower($stringStatus);
753 | $status = array_search($stringStatus, $this->_statuses);
754 | if ($status !== false) {
755 | return $status;
756 | }
757 | return false;
758 | }
759 |
760 | /**
761 | * Reschedule a task based on reschedule
762 | * @param string uuid
763 | * @return boolean success
764 | */
765 | private function __reschedule($id = null) {
766 | if ($id) {
767 | $this->id = $id;
768 | }
769 | if (!$this->exists()) {
770 | return $this->__errorAndExit("QueueTask {$this->id} not found.");
771 | }
772 |
773 | QueueUtil::writeLog('Rescheduling ' . $this->id . ' to ');
774 | }
775 | }
776 |
--------------------------------------------------------------------------------