├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── app ├── code │ └── community │ │ └── Jowens │ │ └── JobQueue │ │ ├── Block │ │ └── Adminhtml │ │ │ ├── Job │ │ │ └── View.php │ │ │ ├── Queue.php │ │ │ └── Queue │ │ │ └── Grid.php │ │ ├── Helper │ │ └── Data.php │ │ ├── Model │ │ ├── Job.php │ │ ├── Job │ │ │ └── Abstract.php │ │ ├── Resource │ │ │ ├── Job.php │ │ │ ├── Job │ │ │ │ └── Collection.php │ │ │ └── Setup.php │ │ ├── Source │ │ │ └── LogLevel.php │ │ └── Worker.php │ │ ├── controllers │ │ └── Adminhtml │ │ │ └── QueueController.php │ │ ├── etc │ │ ├── adminhtml.xml │ │ ├── config.xml │ │ └── system.xml │ │ └── sql │ │ └── jobqueue_setup │ │ ├── mysql4-install-0.1.0.php │ │ └── mysql4-upgrade-0.4.0-0.5.0.php ├── design │ └── adminhtml │ │ └── default │ │ └── default │ │ ├── layout │ │ └── jowens │ │ │ └── jobqueue.xml │ │ └── template │ │ └── jowens │ │ └── jobqueue │ │ └── job.phtml └── etc │ └── modules │ └── Jowens_JobQueue.xml ├── composer.json └── modman /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/DJJob"] 2 | path = lib/DJJob 3 | url = https://github.com/seatgeek/djjob.git 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jordan Owens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JobQueue 2 | 3 | JobQueue allows Magento applications to place tasks in a queue to be processed 4 | asynchronously. It is built on DJJob and makes use of the existing MySQL backend. 5 | Some tasks this may be ideal for are: 6 | 7 | * Downloading files 8 | * Processing batch jobs 9 | * Sending data to a back-office application or third party systems 10 | 11 | ### System Requirements 12 | 13 | * PHP 5.1 or higher 14 | * MySQL 4.1.20 or higher 15 | * Magento CE1.6.0-1.9.x/EE1.7.0-1.14.x 16 | 17 | ### Usage 18 | 19 | Jobs must extend Jowens_JobQueue_Model_Job_Abstract and implement the perform() method. 20 | 21 | class Foo_Bar_Model_Order_Job extends Jowens_JobQueue_Model_Job_Abstract 22 | { 23 | public function perform() { 24 | // implementation logic 25 | } 26 | } 27 | 28 | That job can then be used like so: 29 | 30 | $job = Mage::getModel('bar/order_job'); 31 | $job->setName('Order# 12345') 32 | ->enqueue(); 33 | 34 | Name is used to identify the job in backend, so be descriptive! The enqueue method can take two optional parameters, a string for queue name and timestamp to specify a time to run the job. 35 | 36 | The job can also be attempted immediately. If it fails it is added to 37 | the default queue for retry. 38 | 39 | $job = Mage::getModel('bar/order_job'); 40 | $job->setName('Order# 12345') 41 | ->performImmediate(); 42 | 43 | To put the job on a queue other than the default one, performImmediate 44 | takes an optional string value for the name of the retry queue. 45 | 46 | ### Running Jobs 47 | 48 | JobQueue requires Magento cron to be configured in order to run pending jobs. By default a JobQueue worker executes the pending jobs every 5 minutes. If a job fails it will be retried up to 10 times. Both of these settings can be configured in the admin panel under System > Configuration > General > JobQueue. 49 | 50 | Jobs in other queues can be executed by adding more cron entries to a custom module config.xml. 51 | 52 | 53 | 54 | 55 | 56 | jobqueue/config/cron_expr 57 | 58 | 59 | jobqueue/worker::executeJobs 60 | 61 | orders 62 | 63 | 64 | 65 | 66 | Alternatively workers could be configured to run as they normally would using DJJob. See the [documentation](https://github.com/seatgeek/djjob#running-the-jobs). 67 | 68 | ### Monitoring Jobs 69 | 70 | Pending and failed jobs can be monitored in the admin panel by going to System > JobQueue. 71 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Block/Adminhtml/Job/View.php: -------------------------------------------------------------------------------- 1 | _job = Mage::registry('jowens_jobqueue_job'); 11 | 12 | $this->_blockGroup = 'jobqueue'; 13 | $this->_controller = 'adminhtml_job'; 14 | 15 | parent::__construct(); 16 | 17 | $this->_addButton('back', array( 18 | 'label' => Mage::helper('catalog')->__('Back'), 19 | 'onclick' => 'setLocation(\''.$this->getUrl('*/*/', array('store'=>$this->getRequest()->getParam('store', 0))).'\')', 20 | 'class' => 'back' 21 | ), 0, -20); 22 | $confirmMsg = $this->__('Are you sure you want to do this?'); 23 | $resubmitUrl = $this->getUrl('*/*/resubmit', array('id' => $this->_job->getId())); 24 | $this->_addButton('resubmit', array( 25 | 'label' => $this->__('Resubmit'), 26 | 'onclick' => "confirmSetLocation('{$confirmMsg}', '{$resubmitUrl}')", 27 | ), 0, -10); 28 | 29 | if(!$this->_job->getFailedAt()) { 30 | $cancelUrl = $this->getUrl('*/*/cancel', array('id' => $this->_job->getId())); 31 | $this->_addButton('cancel', array( 32 | 'label' => $this->__('Cancel'), 33 | 'onclick' => "confirmSetLocation('{$confirmMsg}', '{$cancelUrl}')", 34 | ), 0, -5); 35 | } 36 | } 37 | 38 | public function getHeaderText() 39 | { 40 | return $this->__("Job: \"%s\"", $this->_job->getName()); 41 | } 42 | 43 | protected function _toHtml() 44 | { 45 | $this->setJobIdHtml($this->escapeHtml($this->_job->getId())); 46 | $this->setJobNameHtml($this->escapeHtml($this->_job->getName())); 47 | $this->setJobNameHtml($this->escapeHtml($this->_job->getName())); 48 | 49 | $storeId = $this->_job->getStoreId(); 50 | $store = Mage::app()->getStore($storeId); 51 | $this->setStoreNameHtml($this->escapeHtml($store->getName())); 52 | 53 | $this->setJobQueueHtml($this->escapeHtml($this->_job->getQueue())); 54 | $this->setAttemptsHtml($this->escapeHtml($this->_job->getAttempts())); 55 | 56 | $runAt = (strtotime($this->_job->getRunAt())) 57 | ? $this->formatDate($this->_job->getRunAt(), Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, true) 58 | : $this->__('N/A'); 59 | $this->setRunAtHtml($this->escapeHtml($runAt)); 60 | 61 | $status = $this->__("Pending"); 62 | if( $this->_job->getFailedAt()) { 63 | $status = $this->__('Failed'); 64 | } else if($this->_job->getLockedAt()) { 65 | $status = $this->__('In Process'); 66 | } 67 | $this->setStatusHtml($this->escapeHtml($status)); 68 | 69 | $this->setErrorHtml($this->escapeHtml($this->_job->getError())); 70 | 71 | $createdAt = (strtotime($this->_job->getCreatedAt())) 72 | ? $this->formatDate($this->_job->getCreatedAt(), Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, true) 73 | : $this->__('N/A'); 74 | $this->setCreatedAtHtml($this->escapeHtml($createdAt)); 75 | return parent::_toHtml(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Block/Adminhtml/Queue.php: -------------------------------------------------------------------------------- 1 | _blockGroup = 'jobqueue'; 8 | $this->_controller = 'adminhtml_queue'; 9 | $this->_headerText = $this->__('JobQueue'); 10 | 11 | parent::__construct(); 12 | 13 | $this->removeButton('add'); 14 | } 15 | } -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Block/Adminhtml/Queue/Grid.php: -------------------------------------------------------------------------------- 1 | setDefaultSort('created_at'); 10 | $this->setId('jowens_jobqueue_grid'); 11 | $this->setDefaultDir('desc'); 12 | $this->setSaveParametersInSession(true); 13 | } 14 | 15 | protected function _getCollectionClass() 16 | { 17 | return 'jobqueue/job_collection'; 18 | } 19 | 20 | protected function _prepareCollection() 21 | { 22 | $collection = Mage::getModel('jobqueue/job')->getCollection(); 23 | //$collection->getSelect()->columns('(`main_table`.`failed_at` is null) as status'); 24 | $collection->getSelect()->columns("(case when main_table.locked_at is not null then 2 when main_table.failed_at is null then 1 else 0 end) as status"); 25 | $this->setCollection($collection); 26 | 27 | return parent::_prepareCollection(); 28 | } 29 | 30 | protected function _addColumnFilterToCollection($column) 31 | { 32 | if ($column->getId() == 'status') { 33 | $value = $column->getFilter()->getValue(); 34 | if($value == '2') { 35 | $this->getCollection()->addFieldToFilter('locked_at', array('notnull'=> true)); 36 | } else { 37 | $condition = $value == '1' ? 'null' : 'notnull'; 38 | $this->getCollection()->addFieldToFilter('failed_at', array($condition => true)); 39 | $this->getCollection()->addFieldToFilter('locked_at', array('null'=> true)); 40 | } 41 | } else { 42 | parent::_addColumnFilterToCollection($column); 43 | } 44 | return $this; 45 | } 46 | 47 | 48 | protected function _prepareColumns() 49 | { 50 | $this->addColumn('id', 51 | array( 52 | 'header'=> $this->__('ID'), 53 | 'align' => 'right', 54 | 'type' => 'number', 55 | 'width' => '50px', 56 | 'index' => 'id' 57 | ) 58 | ); 59 | 60 | if (!Mage::app()->isSingleStoreMode()) { 61 | $this->addColumn('store_id', array( 62 | 'header' => $this->__('Store'), 63 | 'index' => 'store_id', 64 | 'type' => 'store', 65 | 'store_view'=> true, 66 | 'width' => '200px', 67 | )); 68 | } 69 | 70 | $this->addColumn('name', 71 | array( 72 | 'header'=> $this->__('Name'), 73 | 'index' => 'name' 74 | ) 75 | ); 76 | 77 | $this->addColumn('queue', 78 | array( 79 | 'header'=> $this->__('Queue'), 80 | 'index' => 'queue', 81 | 'align' => 'center', 82 | 'width' => '80px', 83 | ) 84 | ); 85 | 86 | $this->addColumn('created_at', 87 | array( 88 | 'header'=> $this->__('Created At'), 89 | 'index' => 'created_at', 90 | 'type' => 'datetime', 91 | 'width' => '175px', 92 | 'align' => 'center', 93 | ) 94 | ); 95 | 96 | $this->addColumn('run_at', 97 | array( 98 | 'header'=> $this->__('Run At'), 99 | 'index' => 'run_at', 100 | 'type' => 'datetime', 101 | 'align' => 'center', 102 | ) 103 | ); 104 | 105 | $this->addColumn('attempts', 106 | array( 107 | 'header'=> $this->__('Attempts'), 108 | 'index' => 'attempts', 109 | 'type' => 'number', 110 | 'align' => 'center', 111 | 'width' => '100px', 112 | ) 113 | ); 114 | 115 | $this->addColumn('status', 116 | array( 117 | 'header'=> $this->__('Status'), 118 | 'index' => 'status', 119 | 'type' => 'options', 120 | 'options' => array('1'=>'Pending', '2'=>'In Process', '0'=>'Failed'), 121 | 'align' => 'center', 122 | 'width' => '80px', 123 | ) 124 | ); 125 | 126 | $this->addColumn('action', 127 | array( 128 | 'header' => $this->__('Action'), 129 | 'width' => '50px', 130 | 'type' => 'action', 131 | 'getter' => 'getId', 132 | 'actions' => array( 133 | array( 134 | 'caption' => $this->__('View'), 135 | 'url' => array('base'=>'*/*/view'), 136 | 'field' => 'id' 137 | ) 138 | ), 139 | 'filter' => false, 140 | 'sortable' => false, 141 | 'align' => 'center', 142 | ) 143 | ); 144 | 145 | return parent::_prepareColumns(); 146 | } 147 | 148 | protected function _prepareMassaction() 149 | { 150 | $this->setMassactionIdField('id'); 151 | $this->getMassactionBlock()->setFormFieldName('job_id'); 152 | 153 | $this->getMassactionBlock()->addItem('resubmit_job', array( 154 | 'label' => $this->__('Resubmit Job'), 155 | 'url' => $this->getUrl('*/*/massResubmitJob'), 156 | 'confirm' => $this->__('Are you sure?') 157 | )); 158 | 159 | $this->getMassactionBlock()->addItem('cancel_job', array( 160 | 'label' => $this->__('Cancel Job'), 161 | 'url' => $this->getUrl('*/*/massCancelJob'), 162 | 'confirm' => $this->__('Are you sure?') 163 | )); 164 | 165 | $this->getMassactionBlock()->addItem('delete_job', array( 166 | 'label' => $this->__('Delete Job'), 167 | 'url' => $this->getUrl('*/*/massDeleteJob'), 168 | 'confirm' => $this->__('Are you sure?') 169 | )); 170 | 171 | return $this; 172 | } 173 | 174 | public function getRowUrl($row) 175 | { 176 | return $this->getUrl('*/*/view', array('id' => $row->getId())); 177 | } 178 | } -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Helper/Data.php: -------------------------------------------------------------------------------- 1 | _init('jobqueue/job'); 8 | } 9 | 10 | public function resubmit() { 11 | $this->setFailedAt(null); 12 | $this->setRunAt(null); 13 | $this->setAttempts(0); 14 | $this->setError(null); 15 | $this->save(); 16 | } 17 | 18 | public function cancel() { 19 | $this->setFailedAt(Mage::getModel('core/date')->timestamp(time())); 20 | $this->setError(Mage::helper('jobqueue')->__("Job canceled")); 21 | $this->save(); 22 | } 23 | } -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Model/Job/Abstract.php: -------------------------------------------------------------------------------- 1 | name = $name ? $name : $this->getType(); 10 | $this->setStoreId(Mage::app()->getStore()->getStoreId()); 11 | } 12 | 13 | public abstract function perform(); 14 | 15 | public function performImmediate($retryQueue="default") { 16 | try { 17 | $this->perform(); 18 | } catch(Exception $e) { 19 | $this->enqueue($retryQueue); 20 | Mage::logException($e); 21 | } 22 | } 23 | 24 | public function enqueue($queue="default", $run_at=null) { 25 | $job = Mage::getModel('jobqueue/job'); 26 | $job->setStoreId($this->getStoreId()); 27 | $job->setName($this->getName()); 28 | $job->setHandler(serialize($this)); 29 | $job->setQueue($queue); 30 | $job->setRunAt($run_at); 31 | $job->setCreatedAt(now()); 32 | $job->save(); 33 | } 34 | 35 | public function setName($name) 36 | { 37 | $this->name = $name; 38 | return $this; 39 | } 40 | 41 | public function getName() 42 | { 43 | return $this->name; 44 | } 45 | 46 | public function setStoreId($storeId) 47 | { 48 | $this->storeId = $storeId; 49 | return $this; 50 | } 51 | 52 | public function getStoreId() 53 | { 54 | return $this->storeId; 55 | } 56 | 57 | public function getType() 58 | { 59 | return get_class($this); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Model/Resource/Job.php: -------------------------------------------------------------------------------- 1 | _init('jobqueue/job', 'id'); 8 | } 9 | } -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Model/Resource/Job/Collection.php: -------------------------------------------------------------------------------- 1 | _init('jobqueue/job'); 8 | } 9 | } -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Model/Resource/Setup.php: -------------------------------------------------------------------------------- 1 | 'DEBUG', 19 | DJBase::INFO => 'INFO', 20 | DJBase::WARN => 'WARN', 21 | DJBase::ERROR => 'ERROR', 22 | DJBase::CRITICAL => 'CRITICAL' 23 | ]; 24 | } 25 | } -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/Model/Worker.php: -------------------------------------------------------------------------------- 1 | workerName = "host::$hostname pid::$pid"; 20 | $this->queue = Mage::getStoreConfig('jobqueue/config/queue'); 21 | if(empty($this->queue)) { 22 | $this->queue = self::DEFAULT_QUEUE; 23 | } 24 | } 25 | 26 | public function getQueue() { 27 | return $this->queue; 28 | } 29 | 30 | public function setQueue($queue) { 31 | $this->queue = $queue; 32 | } 33 | 34 | public function getWorkerName() { 35 | return $this->workerName; 36 | } 37 | 38 | public function executeJobs($schedule=null) { 39 | if(!Mage::getStoreConfig('jobqueue/config/enabled')) { 40 | return; 41 | } 42 | 43 | if($schedule) { 44 | $jobsRoot = Mage::getConfig()->getNode('crontab/jobs'); 45 | $jobConfig = $jobsRoot->{$schedule->getJobCode()}; 46 | $queue = $jobConfig->queue; 47 | if($queue) { 48 | $this->setQueue($queue); 49 | } 50 | } 51 | 52 | $this->setupDJJob(); 53 | 54 | try { 55 | $collection = Mage::getModel('jobqueue/job')->getCollection(); 56 | $collection->addFieldToFilter('queue', array('eq' => $this->getQueue())) 57 | ->addFieldToFilter('run_at', array( 58 | array('null' => true), 59 | array('lteq' => now()) 60 | )) 61 | ->addFieldToFilter(array('locked_at', 'locked_by'), array( 62 | array('locked_at', 'null' => true), 63 | array('locked_by', 'eq' => $this->workerName) 64 | )) 65 | ->addFieldToFilter('failed_at', array('null' => true)) 66 | ->addFieldToFilter('attempts', array('lt' => (int)Mage::getStoreConfig('jobqueue/config/max_attempts'))) 67 | ->setOrder('id','ASC'); 68 | 69 | $limit = Mage::getStoreConfig('jobqueue/config/limit_jobs'); 70 | if ($limit > 0) { 71 | $collection->getSelect()->limit($limit); 72 | } 73 | 74 | $collection->load(); 75 | 76 | foreach($collection as $row) { 77 | $job = new DJJob($this->workerName, $row->getId(), array( 78 | "max_attempts" => Mage::getStoreConfig('jobqueue/config/max_attempts') 79 | )); 80 | if ($job->acquireLock()) { 81 | $job->run(); 82 | } 83 | } 84 | } catch (Exception $e) { 85 | Mage::logException($e); 86 | } 87 | } 88 | 89 | protected function setupDJJob() { 90 | $config = Mage::getConfig()->getResourceConnectionConfig("default_setup"); 91 | 92 | $dsn = ""; 93 | if (strpos($config->host, '/') !== false) { 94 | $dsn = "mysql:unix_socket=" . $config->host . ";dbname=" . $config->dbname; 95 | } 96 | elseif (strpos($config->host, ':') !== false) { 97 | list($host, $port) = explode(':', $config->host); 98 | $dsn = "mysql:host=" . $host . ";dbname=" . $config->dbname . ";port=" . $port; 99 | } else { 100 | $dsn = "mysql:host=" . $config->host . ";dbname=" . $config->dbname . ";port=" . $config->port; 101 | } 102 | 103 | DJJob::configure( 104 | $dsn, 105 | array('mysql_user' => $config->username, 'mysql_pass' => $config->password), 106 | Mage::getSingleton('core/resource')->getTableName('jobqueue/job') 107 | ); 108 | 109 | if(!empty($config->initStatements)) { 110 | DJJob::runQuery($config->initStatements); 111 | } 112 | 113 | $logLevel = (int) Mage::getStoreConfig('jobqueue/config/log_level'); 114 | DJBase::setLogLevel($logLevel); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/controllers/Adminhtml/QueueController.php: -------------------------------------------------------------------------------- 1 | _init() 8 | ->renderLayout(); 9 | } 10 | 11 | protected function _init() 12 | { 13 | $this->loadLayout() 14 | ->_setActiveMenu('system/jowens_jobqueue_queue') 15 | ->_title($this->__('System'))->_title($this->__('JobQueue')) 16 | ->_addBreadcrumb($this->__('System'), $this->__('System')) 17 | ->_addBreadcrumb($this->__('JobQueue'), $this->__('JobQueue')); 18 | 19 | return $this; 20 | } 21 | 22 | public function viewAction() 23 | { 24 | $id = $this->getRequest()->getParam('id'); 25 | $job = Mage::getModel('jobqueue/job'); 26 | 27 | if ($id) { 28 | $job->load($id); 29 | 30 | if (!$job->getId()) { 31 | Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.')); 32 | $this->_redirect('*/*/index'); 33 | return; 34 | } 35 | } 36 | 37 | $this->_title($job->getId() ? $job->getName() : "Job Details"); 38 | 39 | $data = Mage::getSingleton('adminhtml/session')->getJobData(true); 40 | if (!empty($data)) { 41 | $job->setData($data); 42 | } 43 | 44 | Mage::register('jowens_jobqueue_job', $job); 45 | 46 | $this->_init() 47 | ->renderLayout(); 48 | } 49 | 50 | public function resubmitAction() 51 | { 52 | $id = $this->getRequest()->getParam('id'); 53 | $job = Mage::getModel('jobqueue/job'); 54 | 55 | if ($id) { 56 | $job->load($id); 57 | 58 | if (!$job->getId()) { 59 | Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.')); 60 | $this->_redirect('*/*/index'); 61 | return; 62 | } 63 | 64 | try { 65 | $job->resubmit(); 66 | Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been resubmitted', $job->getName())); 67 | } catch (Exception $e) { 68 | Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be resubmitted', $job->getName())); 69 | } 70 | } 71 | $this->_redirect('*/*/index'); 72 | } 73 | 74 | public function cancelAction() 75 | { 76 | $id = $this->getRequest()->getParam('id'); 77 | $job = Mage::getModel('jobqueue/job'); 78 | 79 | if ($id) { 80 | $job->load($id); 81 | 82 | if (!$job->getId()) { 83 | Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.')); 84 | $this->_redirect('*/*/index'); 85 | return; 86 | } 87 | 88 | try { 89 | $job->cancel(); 90 | Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been canceled', $job->getName())); 91 | } catch (Exception $e) { 92 | Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be canceled', $job->getName())); 93 | } 94 | } 95 | $this->_redirect('*/*/index'); 96 | } 97 | 98 | public function deleteAction() 99 | { 100 | $id = $this->getRequest()->getParam('id'); 101 | $job = Mage::getModel('jobqueue/job'); 102 | 103 | if ($id) { 104 | $job->load($id); 105 | 106 | if (!$job->getId()) { 107 | Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.')); 108 | $this->_redirect('*/*/index'); 109 | return; 110 | } 111 | 112 | try { 113 | $job->delete(); 114 | Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been deleted', $job->getName())); 115 | } catch (Exception $e) { 116 | Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be deleted', $job->getName())); 117 | } 118 | } 119 | $this->_redirect('*/*/index'); 120 | } 121 | 122 | public function massResubmitJobAction() 123 | { 124 | $jobIds = $this->getRequest()->getParam('job_id'); 125 | $success = 0; 126 | $error = 0; 127 | 128 | foreach($jobIds as $jobId) { 129 | $job = Mage::getModel('jobqueue/job')->load($jobId); 130 | try { 131 | $job->resubmit(); 132 | $success++; 133 | } catch (Exception $e) { 134 | Mage::logException($e); 135 | $error++; 136 | } 137 | } 138 | 139 | 140 | if($error) { 141 | Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be resubmitted', $error)); 142 | } 143 | 144 | if($success) { 145 | Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) resubmitted', $success)); 146 | } 147 | 148 | $this->_redirect('*/*/index'); 149 | } 150 | 151 | public function massCancelJobAction() 152 | { 153 | $jobIds = $this->getRequest()->getParam('job_id'); 154 | $success = 0; 155 | $error = 0; 156 | 157 | foreach($jobIds as $jobId) { 158 | $job = Mage::getModel('jobqueue/job')->load($jobId); 159 | try { 160 | if($job->getFailedAt()) { 161 | $error++; 162 | } else { 163 | $job->cancel(); 164 | $success++; 165 | } 166 | } catch (Exception $e) { 167 | Mage::logException($e); 168 | $error++; 169 | } 170 | } 171 | 172 | 173 | if($error) { 174 | Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be canceled', $error)); 175 | } 176 | 177 | if($success) { 178 | Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) canceled', $success)); 179 | } 180 | 181 | $this->_redirect('*/*/index'); 182 | } 183 | 184 | public function massDeleteJobAction() 185 | { 186 | $jobIds = $this->getRequest()->getParam('job_id'); 187 | $success = 0; 188 | $error = 0; 189 | 190 | foreach($jobIds as $jobId) { 191 | $job = Mage::getModel('jobqueue/job')->load($jobId); 192 | try { 193 | $job->delete(); 194 | $success++; 195 | } catch (Exception $e) { 196 | Mage::logException($e); 197 | $error++; 198 | } 199 | } 200 | 201 | 202 | if($error) { 203 | Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be deleted', $error)); 204 | } 205 | 206 | if($success) { 207 | Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) deleted', $success)); 208 | } 209 | 210 | $this->_redirect('*/*/index'); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/etc/adminhtml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JobQueue 8 | adminhtml/queue 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | JobQueue 22 | 23 | 24 | 25 | 26 | JobQueue Configuration 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0.8.0 6 | 7 | 8 | 9 | 10 | 11 | Jowens_JobQueue_Model 12 | jobqueue_resource 13 | 14 | 15 | Jowens_JobQueue_Model_Resource 16 | 17 | 18 | jobs
19 |
20 |
21 |
22 |
23 | 24 | 25 | Jowens_JobQueue_Block 26 | 27 | 28 | 29 | 30 | Jowens_JobQueue_Helper 31 | 32 | 33 | 34 | 35 | 36 | Jowens_JobQueue 37 | Jowens_JobQueue_Model_Resource_Setup 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 | standard 46 | 47 | Jowens_JobQueue 48 | jobqueue 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Jowens_JobQueue_Adminhtml 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | jowens/jobqueue.xml 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | jobqueue/config/cron_expr 78 | 79 | 80 | jobqueue/worker::executeJobs 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 1 89 | */5 * * * * 90 | 10 91 | 1000 92 | default 93 | 0 94 | 95 | 96 | 97 |
98 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/etc/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | general 7 | text 8 | 10000 9 | 1 10 | 11 | 12 | 13 | text 14 | 2 15 | 1 16 | 17 | 18 | 19 | select 20 | adminhtml/system_config_source_yesno 21 | 1 22 | 23 | 24 | 25 | text 26 | 40 27 | Use Crontab Format (Eg. "*/5 * * * *" for every 5 minutes) 28 | 1 29 | 30 | 31 | 32 | text 33 | 50 34 | 1 35 | 36 | 37 | 38 | text 39 | 51 40 | 1 41 | Set to 0 to have no limit 42 | 43 | 44 | 45 | text 46 | 60 47 | 1 48 | 49 | 50 | 51 | select 52 | jobqueue/source_logLevel 53 | 70 54 | Minimal loglevel for logging to occur 55 | 1 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/sql/jobqueue_setup/mysql4-install-0.1.0.php: -------------------------------------------------------------------------------- 1 | startSetup(); 6 | 7 | $installer->run( 8 | "CREATE TABLE " . $installer->getTable('jobqueue/job')." ( 9 | `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 10 | `store_id` INT UNSIGNED NOT NULL DEFAULT 0, 11 | `name` VARCHAR(255), 12 | `handler` TEXT NOT NULL, 13 | `queue` VARCHAR(255) NOT NULL DEFAULT 'default', 14 | `attempts` INT UNSIGNED NOT NULL DEFAULT 0, 15 | `run_at` DATETIME NULL, 16 | `locked_at` DATETIME NULL, 17 | `locked_by` VARCHAR(255) NULL, 18 | `failed_at` DATETIME NULL, 19 | `error` TEXT NULL, 20 | `created_at` DATETIME NOT NULL 21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;" 22 | ); 23 | 24 | $installer->endSetup(); -------------------------------------------------------------------------------- /app/code/community/Jowens/JobQueue/sql/jobqueue_setup/mysql4-upgrade-0.4.0-0.5.0.php: -------------------------------------------------------------------------------- 1 | getConnection(); 6 | 7 | $installer->startSetup(); 8 | 9 | $connection->modifyColumn( 10 | $installer->getTable('jobqueue/job'), 11 | 'handler', 12 | 'BLOB NOT NULL' 13 | ); 14 | 15 | $installer->endSetup(); -------------------------------------------------------------------------------- /app/design/adminhtml/default/default/layout/jowens/jobqueue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/design/adminhtml/default/default/template/jowens/jobqueue/job.phtml: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |
getHeaderHtml() ?>getButtonsHtml() ?>
8 |
9 | 10 |
11 |
12 |
13 |

__('Job Details'); ?>

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 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 | 48 | 49 | 50 | 51 | 52 | getErrorHtml()): ?> 53 | 54 | 55 | 56 | 57 | 58 | 59 |
__('Job ID'); ?>getJobIdHtml(); ?>
__('Job Name'); ?>getJobNameHtml(); ?>
__('Store'); ?>getStoreNameHtml(); ?>
__('Queue'); ?>getJobQueueHtml(); ?>
__('Run At'); ?>getRunAtHtml(); ?>
__('Attempts'); ?>getAttemptsHtml(); ?>
__('Status'); ?>getStatusHtml(); ?>
__('Created At'); ?>getCreatedAtHtml(); ?>
__('Error'); ?>getErrorHtml(); ?>
60 |
61 |
62 |
63 | -------------------------------------------------------------------------------- /app/etc/modules/Jowens_JobQueue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | community 7 | 8 | 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jkowens/magento-jobqueue", 3 | "license": "MIT", 4 | "type": "magento-module", 5 | "version": "0.8.0", 6 | "description": "JobQueue for Magento using DJJob", 7 | "require": { 8 | "php": ">=5.1", 9 | "seatgeek/djjob": "1.0.0" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "Jordan Owens" 14 | } 15 | ], 16 | "extra": { 17 | "map": [ 18 | ["app/code/community/Jowens/JobQueue/", "app/code/community/Jowens/JobQueue/"], 19 | ["app/design/adminhtml/default/default/layout/jowens/jobqueue.xml", "app/design/adminhtml/default/default/layout/jowens/jobqueue.xml"], 20 | ["app/design/adminhtml/default/default/template/jowens/jobqueue/", "app/design/adminhtml/default/default/template/jowens/jobqueue/"], 21 | ["app/etc/modules/Jowens_JobQueue.xml", "app/etc/modules/Jowens_JobQueue.xml"] 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modman: -------------------------------------------------------------------------------- 1 | app/code/community/Jowens/JobQueue app/code/community/Jowens/JobQueue 2 | app/design/adminhtml/default/default/layout/jowens app/design/adminhtml/default/default/layout/jowens 3 | app/design/adminhtml/default/default/template/jowens app/design/adminhtml/default/default/template/jowens 4 | app/etc/modules/* app/etc/modules/ 5 | lib/DJJob lib/DJJob --------------------------------------------------------------------------------