Version Notes
First stable release.
Download this release
Release Info
Developer | Pavel Zhytomirsky |
Extension | Wisepricer_Syncer2 |
Version | 2.0.0.0 |
Comparing to | |
See all releases |
Version 2.0.0.0
- app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Job/View.php +95 -0
- app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue.php +38 -0
- app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue/Grid.php +201 -0
- app/code/community/Wisepricer/JobQueue/Helper/Data.php +28 -0
- app/code/community/Wisepricer/JobQueue/Model/Job.php +46 -0
- app/code/community/Wisepricer/JobQueue/Model/Job/Abstract.php +84 -0
- app/code/community/Wisepricer/JobQueue/Model/Resource/Job.php +32 -0
- app/code/community/Wisepricer/JobQueue/Model/Resource/Job/Collection.php +32 -0
- app/code/community/Wisepricer/JobQueue/Model/Resource/Setup.php +28 -0
- app/code/community/Wisepricer/JobQueue/Model/Worker.php +125 -0
- app/code/community/Wisepricer/JobQueue/controllers/Adminhtml/QueueController.php +235 -0
- app/code/community/Wisepricer/JobQueue/etc/config.xml +98 -0
- app/code/community/Wisepricer/JobQueue/sql/wisepricer_jobqueue_setup/mysql4-install-1.0.0.0.php +47 -0
- app/code/community/Wisepricer/Syncer2/Exception.php +6 -0
- app/code/community/Wisepricer/Syncer2/Helper/Data.php +82 -0
- app/code/community/Wisepricer/Syncer2/Model/Abstract.php +129 -0
- app/code/community/Wisepricer/Syncer2/Model/Exporter.php +141 -0
- app/code/community/Wisepricer/Syncer2/Model/Importer.php +246 -0
- app/code/community/Wisepricer/Syncer2/controllers/ApiController.php +348 -0
- app/code/community/Wisepricer/Syncer2/etc/adminhtml.xml +22 -0
- app/code/community/Wisepricer/Syncer2/etc/config.xml +75 -0
- app/code/community/Wisepricer/Syncer2/etc/system.xml +55 -0
- app/etc/modules/Wisepricer_Syncer2.xml +16 -0
- lib/DJJob/DJJob.php +525 -0
- lib/DJJob/examples/HelloWorldJob.php +13 -0
- lib/DJJob/jobs.sql +12 -0
- lib/DJJob/test/custom_table_name.php +82 -0
- lib/DJJob/test/database.php +84 -0
- lib/DJJob/test/original_database_configure.php +79 -0
- package.xml +18 -0
- skin/adminhtml/default/default/images/wisepricer_syncer2/wiser_logo.png +0 -0
app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Job/View.php
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Block_Adminhtml_Job_View extends Mage_Adminhtml_Block_Widget_Container
|
27 |
+
{
|
28 |
+
|
29 |
+
protected $_job;
|
30 |
+
|
31 |
+
public function __construct()
|
32 |
+
{
|
33 |
+
$this->_job = Mage::registry('wisepricer_jobqueue_job');
|
34 |
+
|
35 |
+
$this->_blockGroup = 'jobqueue';
|
36 |
+
$this->_controller = 'adminhtml_job';
|
37 |
+
|
38 |
+
parent::__construct();
|
39 |
+
|
40 |
+
$confirmMsg = $this->__('Are you sure you want to do this?');
|
41 |
+
$resubmitUrl = $this->getUrl('*/*/resubmit', array('id' => $this->_job->getId()));
|
42 |
+
$this->_addButton('resubmit', array(
|
43 |
+
'label' => $this->__('Resubmit'),
|
44 |
+
'onclick' => "confirmSetLocation('{$confirmMsg}', '{$resubmitUrl}')",
|
45 |
+
), 0, -10);
|
46 |
+
|
47 |
+
if(!$this->_job->getFailedAt()) {
|
48 |
+
$cancelUrl = $this->getUrl('*/*/cancel', array('id' => $this->_job->getId()));
|
49 |
+
$this->_addButton('cancel', array(
|
50 |
+
'label' => $this->__('Cancel'),
|
51 |
+
'onclick' => "confirmSetLocation('{$confirmMsg}', '{$cancelUrl}')",
|
52 |
+
), 0, -5);
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
public function getHeaderText()
|
57 |
+
{
|
58 |
+
return $this->__("Job: \"%s\"", $this->_job->getName());
|
59 |
+
}
|
60 |
+
|
61 |
+
protected function _toHtml()
|
62 |
+
{
|
63 |
+
$this->setJobIdHtml($this->escapeHtml($this->_job->getId()));
|
64 |
+
$this->setJobNameHtml($this->escapeHtml($this->_job->getName()));
|
65 |
+
$this->setJobNameHtml($this->escapeHtml($this->_job->getName()));
|
66 |
+
|
67 |
+
$storeId = $this->_job->getStoreId();
|
68 |
+
$store = Mage::app()->getStore($storeId);
|
69 |
+
$this->setStoreNameHtml($this->escapeHtml($store->getName()));
|
70 |
+
|
71 |
+
$this->setJobQueueHtml($this->escapeHtml($this->_job->getQueue()));
|
72 |
+
$this->setAttemptsHtml($this->escapeHtml($this->_job->getAttempts()));
|
73 |
+
|
74 |
+
$runAt = (strtotime($this->_job->getRunAt()))
|
75 |
+
? $this->formatDate($this->_job->getRunAt(), Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, true)
|
76 |
+
: $this->__('N/A');
|
77 |
+
$this->setRunAtHtml($this->escapeHtml($runAt));
|
78 |
+
|
79 |
+
$status = $this->__("Pending");
|
80 |
+
if( $this->_job->getFailedAt()) {
|
81 |
+
$status = $this->__('Failed');
|
82 |
+
} else if($this->_job->getLockedAt()) {
|
83 |
+
$status = $this->__('In Process');
|
84 |
+
}
|
85 |
+
$this->setStatusHtml($this->escapeHtml($status));
|
86 |
+
|
87 |
+
$this->setErrorHtml($this->escapeHtml($this->_job->getError()));
|
88 |
+
|
89 |
+
$createdAt = (strtotime($this->_job->getCreatedAt()))
|
90 |
+
? $this->formatDate($this->_job->getCreatedAt(), Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, true)
|
91 |
+
: $this->__('N/A');
|
92 |
+
$this->setCreatedAtHtml($this->escapeHtml($createdAt));
|
93 |
+
return parent::_toHtml();
|
94 |
+
}
|
95 |
+
}
|
app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Block_Adminhtml_Queue extends Mage_Adminhtml_Block_Widget_Grid_Container
|
27 |
+
{
|
28 |
+
public function __construct()
|
29 |
+
{
|
30 |
+
$this->_blockGroup = 'jobqueue';
|
31 |
+
$this->_controller = 'adminhtml_queue';
|
32 |
+
$this->_headerText = $this->__('JobQueue');
|
33 |
+
|
34 |
+
parent::__construct();
|
35 |
+
|
36 |
+
$this->removeButton('add');
|
37 |
+
}
|
38 |
+
}
|
app/code/community/Wisepricer/JobQueue/Block/Adminhtml/Queue/Grid.php
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Block_Adminhtml_Queue_Grid extends Mage_Adminhtml_Block_Widget_Grid
|
27 |
+
{
|
28 |
+
public function __construct()
|
29 |
+
{
|
30 |
+
parent::__construct();
|
31 |
+
|
32 |
+
$this->setDefaultSort('created_at');
|
33 |
+
$this->setId('wisepricer_jobqueue_grid');
|
34 |
+
$this->setDefaultDir('desc');
|
35 |
+
$this->setSaveParametersInSession(true);
|
36 |
+
}
|
37 |
+
|
38 |
+
protected function _getCollectionClass()
|
39 |
+
{
|
40 |
+
return 'jobqueue/job_collection';
|
41 |
+
}
|
42 |
+
|
43 |
+
protected function _prepareCollection()
|
44 |
+
{
|
45 |
+
$collection = Mage::getModel('jobqueue/job')->getCollection();
|
46 |
+
//$collection->getSelect()->columns('(`main_table`.`failed_at` is null) as status');
|
47 |
+
$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");
|
48 |
+
$this->setCollection($collection);
|
49 |
+
|
50 |
+
return parent::_prepareCollection();
|
51 |
+
}
|
52 |
+
|
53 |
+
protected function _addColumnFilterToCollection($column)
|
54 |
+
{
|
55 |
+
if ($column->getId() == 'status') {
|
56 |
+
$value = $column->getFilter()->getValue();
|
57 |
+
if($value == '2') {
|
58 |
+
$this->getCollection()->addFieldToFilter('locked_at', array('notnull'=> true));
|
59 |
+
} else {
|
60 |
+
$condition = $value == '1' ? 'null' : 'notnull';
|
61 |
+
$this->getCollection()->addFieldToFilter('failed_at', array($condition => true));
|
62 |
+
$this->getCollection()->addFieldToFilter('locked_at', array('null'=> true));
|
63 |
+
}
|
64 |
+
} else {
|
65 |
+
parent::_addColumnFilterToCollection($column);
|
66 |
+
}
|
67 |
+
return $this;
|
68 |
+
}
|
69 |
+
|
70 |
+
|
71 |
+
protected function _prepareColumns()
|
72 |
+
{
|
73 |
+
$this->addColumn('id',
|
74 |
+
array(
|
75 |
+
'header'=> $this->__('ID'),
|
76 |
+
'align' => 'right',
|
77 |
+
'type' => 'number',
|
78 |
+
'width' => '50px',
|
79 |
+
'index' => 'id'
|
80 |
+
)
|
81 |
+
);
|
82 |
+
|
83 |
+
if (!Mage::app()->isSingleStoreMode()) {
|
84 |
+
$this->addColumn('store_id', array(
|
85 |
+
'header' => $this->__('Store'),
|
86 |
+
'index' => 'store_id',
|
87 |
+
'type' => 'store',
|
88 |
+
'store_view'=> true,
|
89 |
+
'width' => '200px',
|
90 |
+
));
|
91 |
+
}
|
92 |
+
|
93 |
+
$this->addColumn('name',
|
94 |
+
array(
|
95 |
+
'header'=> $this->__('Name'),
|
96 |
+
'index' => 'name'
|
97 |
+
)
|
98 |
+
);
|
99 |
+
|
100 |
+
$this->addColumn('queue',
|
101 |
+
array(
|
102 |
+
'header'=> $this->__('Queue'),
|
103 |
+
'index' => 'queue',
|
104 |
+
'align' => 'center',
|
105 |
+
'width' => '80px',
|
106 |
+
)
|
107 |
+
);
|
108 |
+
|
109 |
+
$this->addColumn('created_at',
|
110 |
+
array(
|
111 |
+
'header'=> $this->__('Created At'),
|
112 |
+
'index' => 'created_at',
|
113 |
+
'type' => 'datetime',
|
114 |
+
'width' => '175px',
|
115 |
+
'align' => 'center',
|
116 |
+
)
|
117 |
+
);
|
118 |
+
|
119 |
+
$this->addColumn('run_at',
|
120 |
+
array(
|
121 |
+
'header'=> $this->__('Run At'),
|
122 |
+
'index' => 'run_at',
|
123 |
+
'type' => 'datetime',
|
124 |
+
'align' => 'center',
|
125 |
+
)
|
126 |
+
);
|
127 |
+
|
128 |
+
$this->addColumn('attempts',
|
129 |
+
array(
|
130 |
+
'header'=> $this->__('Attempts'),
|
131 |
+
'index' => 'attempts',
|
132 |
+
'type' => 'number',
|
133 |
+
'align' => 'center',
|
134 |
+
'width' => '100px',
|
135 |
+
)
|
136 |
+
);
|
137 |
+
|
138 |
+
$this->addColumn('status',
|
139 |
+
array(
|
140 |
+
'header'=> $this->__('Status'),
|
141 |
+
'index' => 'status',
|
142 |
+
'type' => 'options',
|
143 |
+
'options' => array('1'=>'Pending', '2'=>'In Process', '0'=>'Failed'),
|
144 |
+
'align' => 'center',
|
145 |
+
'width' => '80px',
|
146 |
+
)
|
147 |
+
);
|
148 |
+
|
149 |
+
$this->addColumn('action',
|
150 |
+
array(
|
151 |
+
'header' => $this->__('Action'),
|
152 |
+
'width' => '50px',
|
153 |
+
'type' => 'action',
|
154 |
+
'getter' => 'getId',
|
155 |
+
'actions' => array(
|
156 |
+
array(
|
157 |
+
'caption' => $this->__('View'),
|
158 |
+
'url' => array('base'=>'*/*/view'),
|
159 |
+
'field' => 'id'
|
160 |
+
)
|
161 |
+
),
|
162 |
+
'filter' => false,
|
163 |
+
'sortable' => false,
|
164 |
+
'align' => 'center',
|
165 |
+
)
|
166 |
+
);
|
167 |
+
|
168 |
+
return parent::_prepareColumns();
|
169 |
+
}
|
170 |
+
|
171 |
+
protected function _prepareMassaction()
|
172 |
+
{
|
173 |
+
$this->setMassactionIdField('id');
|
174 |
+
$this->getMassactionBlock()->setFormFieldName('job_id');
|
175 |
+
|
176 |
+
$this->getMassactionBlock()->addItem('resubmit_job', array(
|
177 |
+
'label' => $this->__('Resubmit Job'),
|
178 |
+
'url' => $this->getUrl('*/*/massResubmitJob'),
|
179 |
+
'confirm' => $this->__('Are you sure?')
|
180 |
+
));
|
181 |
+
|
182 |
+
$this->getMassactionBlock()->addItem('cancel_job', array(
|
183 |
+
'label' => $this->__('Cancel Job'),
|
184 |
+
'url' => $this->getUrl('*/*/massCancelJob'),
|
185 |
+
'confirm' => $this->__('Are you sure?')
|
186 |
+
));
|
187 |
+
|
188 |
+
$this->getMassactionBlock()->addItem('delete_job', array(
|
189 |
+
'label' => $this->__('Delete Job'),
|
190 |
+
'url' => $this->getUrl('*/*/massDeleteJob'),
|
191 |
+
'confirm' => $this->__('Are you sure?')
|
192 |
+
));
|
193 |
+
|
194 |
+
return $this;
|
195 |
+
}
|
196 |
+
|
197 |
+
public function getRowUrl($row)
|
198 |
+
{
|
199 |
+
return $this->getUrl('*/*/view', array('id' => $row->getId()));
|
200 |
+
}
|
201 |
+
}
|
app/code/community/Wisepricer/JobQueue/Helper/Data.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Helper_Data extends Mage_Core_Helper_Abstract
|
27 |
+
{
|
28 |
+
}
|
app/code/community/Wisepricer/JobQueue/Model/Job.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Model_Job extends Mage_Core_Model_Abstract
|
27 |
+
{
|
28 |
+
protected function _construct()
|
29 |
+
{
|
30 |
+
$this->_init('jobqueue/job');
|
31 |
+
}
|
32 |
+
|
33 |
+
public function resubmit() {
|
34 |
+
$this->setFailedAt(null);
|
35 |
+
$this->setRunAt(null);
|
36 |
+
$this->setAttempts(0);
|
37 |
+
$this->setError(null);
|
38 |
+
$this->save();
|
39 |
+
}
|
40 |
+
|
41 |
+
public function cancel() {
|
42 |
+
$this->setFailedAt(Mage::getModel('core/date')->timestamp(time()));
|
43 |
+
$this->setError(Mage::helper('jobqueue')->__("Job canceled"));
|
44 |
+
$this->save();
|
45 |
+
}
|
46 |
+
}
|
app/code/community/Wisepricer/JobQueue/Model/Job/Abstract.php
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
abstract class Wisepricer_JobQueue_Model_Job_Abstract extends Mage_Core_Model_Abstract
|
27 |
+
{
|
28 |
+
private $name;
|
29 |
+
private $storeId;
|
30 |
+
|
31 |
+
public function __construct($name=null) {
|
32 |
+
$this->name = $name ? $name : $this->getType();
|
33 |
+
$this->setStoreId(Mage::app()->getStore()->getStoreId());
|
34 |
+
}
|
35 |
+
|
36 |
+
public abstract function perform();
|
37 |
+
|
38 |
+
public function performImmediate($retryQueue="default") {
|
39 |
+
try {
|
40 |
+
$this->perform();
|
41 |
+
} catch(Exception $e) {
|
42 |
+
$this->enqueue($retryQueue);
|
43 |
+
Mage::logException($e);
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
public function enqueue($queue="default", $run_at=null) {
|
48 |
+
$job = Mage::getModel('jobqueue/job');
|
49 |
+
$job->setStoreId($this->getStoreId());
|
50 |
+
$job->setName($this->getName());
|
51 |
+
$job->setHandler(serialize($this));
|
52 |
+
$job->setQueue($queue);
|
53 |
+
$job->setRunAt($run_at);
|
54 |
+
$job->setCreatedAt(now());
|
55 |
+
$job->save();
|
56 |
+
}
|
57 |
+
|
58 |
+
public function setName($name)
|
59 |
+
{
|
60 |
+
$this->name = $name;
|
61 |
+
return $this;
|
62 |
+
}
|
63 |
+
|
64 |
+
public function getName()
|
65 |
+
{
|
66 |
+
return $this->name;
|
67 |
+
}
|
68 |
+
|
69 |
+
public function setStoreId($storeId)
|
70 |
+
{
|
71 |
+
$this->storeId = $storeId;
|
72 |
+
return $this;
|
73 |
+
}
|
74 |
+
|
75 |
+
public function getStoreId()
|
76 |
+
{
|
77 |
+
return $this->storeId;
|
78 |
+
}
|
79 |
+
|
80 |
+
public function getType()
|
81 |
+
{
|
82 |
+
return get_class($this);
|
83 |
+
}
|
84 |
+
}
|
app/code/community/Wisepricer/JobQueue/Model/Resource/Job.php
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Model_Resource_Job extends Mage_Core_Model_Resource_Db_Abstract
|
27 |
+
{
|
28 |
+
protected function _construct()
|
29 |
+
{
|
30 |
+
$this->_init('jobqueue/job', 'id');
|
31 |
+
}
|
32 |
+
}
|
app/code/community/Wisepricer/JobQueue/Model/Resource/Job/Collection.php
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Model_Resource_Job_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
|
27 |
+
{
|
28 |
+
protected function _construct()
|
29 |
+
{
|
30 |
+
$this->_init('jobqueue/job');
|
31 |
+
}
|
32 |
+
}
|
app/code/community/Wisepricer/JobQueue/Model/Resource/Setup.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup
|
27 |
+
{
|
28 |
+
}
|
app/code/community/Wisepricer/JobQueue/Model/Worker.php
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
set_include_path(get_include_path().PS.Mage::getBaseDir('lib').DS.'DJJob');
|
27 |
+
|
28 |
+
require_once('DJJob.php');
|
29 |
+
|
30 |
+
class Wisepricer_JobQueue_Model_Worker extends Mage_Core_Model_Abstract
|
31 |
+
{
|
32 |
+
const DEFAULT_QUEUE = 'default';
|
33 |
+
|
34 |
+
private $workerName;
|
35 |
+
private $queue;
|
36 |
+
|
37 |
+
public function __construct() {
|
38 |
+
list($hostname, $pid) = array(trim(`hostname`), getmypid());
|
39 |
+
$this->workerName = "host::$hostname pid::$pid";
|
40 |
+
$this->queue = Mage::getStoreConfig('jobqueue/config/queue');
|
41 |
+
if(empty($this->queue)) {
|
42 |
+
$this->queue = self::DEFAULT_QUEUE;
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
public function getQueue() {
|
47 |
+
return $this->queue;
|
48 |
+
}
|
49 |
+
|
50 |
+
public function setQueue($queue) {
|
51 |
+
$this->queue = $queue;
|
52 |
+
}
|
53 |
+
|
54 |
+
public function getWorkerName() {
|
55 |
+
return $this->workerName;
|
56 |
+
}
|
57 |
+
|
58 |
+
public function executeJobs($schedule=null) {
|
59 |
+
if(!Mage::getStoreConfig('jobqueue/config/enabled')) {
|
60 |
+
return;
|
61 |
+
}
|
62 |
+
|
63 |
+
if($schedule) {
|
64 |
+
$jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
|
65 |
+
$jobConfig = $jobsRoot->{$schedule->getJobCode()};
|
66 |
+
$queue = $jobConfig->queue;
|
67 |
+
if($queue) {
|
68 |
+
$this->setQueue($queue);
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
$this->setupDJJob();
|
73 |
+
|
74 |
+
try {
|
75 |
+
$collection = Mage::getModel('jobqueue/job')->getCollection();
|
76 |
+
$collection->addFieldToFilter('queue', array('eq' => $this->getQueue()))
|
77 |
+
->addFieldToFilter('run_at', array(
|
78 |
+
array('null' => true),
|
79 |
+
array('lteq' => now())
|
80 |
+
))
|
81 |
+
->addFieldToFilter(array('locked_at', 'locked_by'), array(
|
82 |
+
array('locked_at', 'null' => true),
|
83 |
+
array('locked_by', 'eq' => $this->workerName)
|
84 |
+
))
|
85 |
+
->addFieldToFilter('failed_at', array('null' => true))
|
86 |
+
->addFieldToFilter('attempts', array('lt' => (int)Mage::getStoreConfig('jobqueue/config/max_attempts')));
|
87 |
+
|
88 |
+
// randomly order to prevent lock contention among workers
|
89 |
+
$collection->getSelect()->order(new Zend_Db_Expr('RAND()'));
|
90 |
+
$collection->load();
|
91 |
+
|
92 |
+
foreach($collection as $row) {
|
93 |
+
$job = new DJJob($this->workerName, $row->getId(), array(
|
94 |
+
"max_attempts" => Mage::getStoreConfig('jobqueue/config/max_attempts')
|
95 |
+
));
|
96 |
+
if ($job->acquireLock()) {
|
97 |
+
$job->run();
|
98 |
+
}
|
99 |
+
}
|
100 |
+
} catch (Exception $e) {
|
101 |
+
Mage::logException($e);
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
protected function setupDJJob() {
|
106 |
+
$config = Mage::getConfig()->getResourceConnectionConfig("default_setup");
|
107 |
+
|
108 |
+
$dsn = "";
|
109 |
+
if (strpos($config->host, '/') !== false) {
|
110 |
+
$dsn = "mysql:unix_socket=" . $config->host . ";dbname=" . $config->dbname;
|
111 |
+
} else {
|
112 |
+
$dsn = "mysql:host=" . $config->host . ";dbname=" . $config->dbname . ";port=" . $config->port;
|
113 |
+
}
|
114 |
+
|
115 |
+
DJJob::configure(
|
116 |
+
$dsn,
|
117 |
+
array('mysql_user' => $config->username, 'mysql_pass' => $config->password),
|
118 |
+
Mage::getSingleton('core/resource')->getTableName('jobqueue/job')
|
119 |
+
);
|
120 |
+
|
121 |
+
if(!empty($config->initStatements)) {
|
122 |
+
DJJob::runQuery($config->initStatements);
|
123 |
+
}
|
124 |
+
}
|
125 |
+
}
|
app/code/community/Wisepricer/JobQueue/controllers/Adminhtml/QueueController.php
ADDED
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
class Wisepricer_JobQueue_Adminhtml_QueueController extends Mage_Adminhtml_Controller_Action
|
27 |
+
{
|
28 |
+
public function indexAction()
|
29 |
+
{
|
30 |
+
$this->_init()
|
31 |
+
->renderLayout();
|
32 |
+
}
|
33 |
+
|
34 |
+
protected function _init()
|
35 |
+
{
|
36 |
+
$this->loadLayout()
|
37 |
+
->_setActiveMenu('system/wisepricer_jobqueue_queue')
|
38 |
+
->_title($this->__('System'))->_title($this->__('JobQueue'))
|
39 |
+
->_addBreadcrumb($this->__('System'), $this->__('System'))
|
40 |
+
->_addBreadcrumb($this->__('JobQueue'), $this->__('JobQueue'));
|
41 |
+
|
42 |
+
return $this;
|
43 |
+
}
|
44 |
+
|
45 |
+
public function viewAction()
|
46 |
+
{
|
47 |
+
$id = $this->getRequest()->getParam('id');
|
48 |
+
$job = Mage::getModel('jobqueue/job');
|
49 |
+
|
50 |
+
if ($id) {
|
51 |
+
$job->load($id);
|
52 |
+
|
53 |
+
if (!$job->getId()) {
|
54 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
|
55 |
+
$this->_redirect('*/*/index');
|
56 |
+
return;
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
$this->_title($job->getId() ? $job->getName() : "Job Details");
|
61 |
+
|
62 |
+
$data = Mage::getSingleton('adminhtml/session')->getJobData(true);
|
63 |
+
if (!empty($data)) {
|
64 |
+
$job->setData($data);
|
65 |
+
}
|
66 |
+
|
67 |
+
Mage::register('wisepricer_jobqueue_job', $job);
|
68 |
+
|
69 |
+
$this->_init()
|
70 |
+
->renderLayout();
|
71 |
+
}
|
72 |
+
|
73 |
+
public function resubmitAction()
|
74 |
+
{
|
75 |
+
$id = $this->getRequest()->getParam('id');
|
76 |
+
$job = Mage::getModel('jobqueue/job');
|
77 |
+
|
78 |
+
if ($id) {
|
79 |
+
$job->load($id);
|
80 |
+
|
81 |
+
if (!$job->getId()) {
|
82 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
|
83 |
+
$this->_redirect('*/*/index');
|
84 |
+
return;
|
85 |
+
}
|
86 |
+
|
87 |
+
try {
|
88 |
+
$job->resubmit();
|
89 |
+
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been resubmitted', $job->getName()));
|
90 |
+
} catch (Exception $e) {
|
91 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be resubmitted', $job->getName()));
|
92 |
+
}
|
93 |
+
}
|
94 |
+
$this->_redirect('*/*/index');
|
95 |
+
}
|
96 |
+
|
97 |
+
public function cancelAction()
|
98 |
+
{
|
99 |
+
$id = $this->getRequest()->getParam('id');
|
100 |
+
$job = Mage::getModel('jobqueue/job');
|
101 |
+
|
102 |
+
if ($id) {
|
103 |
+
$job->load($id);
|
104 |
+
|
105 |
+
if (!$job->getId()) {
|
106 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
|
107 |
+
$this->_redirect('*/*/index');
|
108 |
+
return;
|
109 |
+
}
|
110 |
+
|
111 |
+
try {
|
112 |
+
$job->cancel();
|
113 |
+
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been canceled', $job->getName()));
|
114 |
+
} catch (Exception $e) {
|
115 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be canceled', $job->getName()));
|
116 |
+
}
|
117 |
+
}
|
118 |
+
$this->_redirect('*/*/index');
|
119 |
+
}
|
120 |
+
|
121 |
+
public function deleteAction()
|
122 |
+
{
|
123 |
+
$id = $this->getRequest()->getParam('id');
|
124 |
+
$job = Mage::getModel('jobqueue/job');
|
125 |
+
|
126 |
+
if ($id) {
|
127 |
+
$job->load($id);
|
128 |
+
|
129 |
+
if (!$job->getId()) {
|
130 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('This job no longer exists.'));
|
131 |
+
$this->_redirect('*/*/index');
|
132 |
+
return;
|
133 |
+
}
|
134 |
+
|
135 |
+
try {
|
136 |
+
$job->delete();
|
137 |
+
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('Job "%s" has been deleted', $job->getName()));
|
138 |
+
} catch (Exception $e) {
|
139 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('Job "%s" could not be deleted', $job->getName()));
|
140 |
+
}
|
141 |
+
}
|
142 |
+
$this->_redirect('*/*/index');
|
143 |
+
}
|
144 |
+
|
145 |
+
public function massResubmitJobAction()
|
146 |
+
{
|
147 |
+
$jobIds = $this->getRequest()->getParam('job_id');
|
148 |
+
$success = 0;
|
149 |
+
$error = 0;
|
150 |
+
|
151 |
+
foreach($jobIds as $jobId) {
|
152 |
+
$job = Mage::getModel('jobqueue/job')->load($jobId);
|
153 |
+
try {
|
154 |
+
$job->resubmit();
|
155 |
+
$success++;
|
156 |
+
} catch (Exception $e) {
|
157 |
+
Mage::logException($e);
|
158 |
+
$error++;
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
|
163 |
+
if($error) {
|
164 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be resubmitted', $error));
|
165 |
+
}
|
166 |
+
|
167 |
+
if($success) {
|
168 |
+
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) resubmitted', $success));
|
169 |
+
}
|
170 |
+
|
171 |
+
$this->_redirect('*/*/index');
|
172 |
+
}
|
173 |
+
|
174 |
+
public function massCancelJobAction()
|
175 |
+
{
|
176 |
+
$jobIds = $this->getRequest()->getParam('job_id');
|
177 |
+
$success = 0;
|
178 |
+
$error = 0;
|
179 |
+
|
180 |
+
foreach($jobIds as $jobId) {
|
181 |
+
$job = Mage::getModel('jobqueue/job')->load($jobId);
|
182 |
+
try {
|
183 |
+
if($job->getFailedAt()) {
|
184 |
+
$error++;
|
185 |
+
} else {
|
186 |
+
$job->cancel();
|
187 |
+
$success++;
|
188 |
+
}
|
189 |
+
} catch (Exception $e) {
|
190 |
+
Mage::logException($e);
|
191 |
+
$error++;
|
192 |
+
}
|
193 |
+
}
|
194 |
+
|
195 |
+
|
196 |
+
if($error) {
|
197 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be canceled', $error));
|
198 |
+
}
|
199 |
+
|
200 |
+
if($success) {
|
201 |
+
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) canceled', $success));
|
202 |
+
}
|
203 |
+
|
204 |
+
$this->_redirect('*/*/index');
|
205 |
+
}
|
206 |
+
|
207 |
+
public function massDeleteJobAction()
|
208 |
+
{
|
209 |
+
$jobIds = $this->getRequest()->getParam('job_id');
|
210 |
+
$success = 0;
|
211 |
+
$error = 0;
|
212 |
+
|
213 |
+
foreach($jobIds as $jobId) {
|
214 |
+
$job = Mage::getModel('jobqueue/job')->load($jobId);
|
215 |
+
try {
|
216 |
+
$job->delete();
|
217 |
+
$success++;
|
218 |
+
} catch (Exception $e) {
|
219 |
+
Mage::logException($e);
|
220 |
+
$error++;
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
|
225 |
+
if($error) {
|
226 |
+
Mage::getSingleton('adminhtml/session')->addError($this->__('%s job(s) could not be deleted', $error));
|
227 |
+
}
|
228 |
+
|
229 |
+
if($success) {
|
230 |
+
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('%s job(s) deleted', $success));
|
231 |
+
}
|
232 |
+
|
233 |
+
$this->_redirect('*/*/index');
|
234 |
+
}
|
235 |
+
}
|
app/code/community/Wisepricer/JobQueue/etc/config.xml
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="utf-8" ?>
|
2 |
+
<!--
|
3 |
+
The MIT License (MIT)
|
4 |
+
|
5 |
+
Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
|
7 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
of this software and associated documentation files (the "Software"), to deal
|
9 |
+
in the Software without restriction, including without limitation the rights
|
10 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
copies of the Software, and to permit persons to whom the Software is
|
12 |
+
furnished to do so, subject to the following conditions:
|
13 |
+
|
14 |
+
The above copyright notice and this permission notice shall be included in
|
15 |
+
all copies or substantial portions of the Software.
|
16 |
+
|
17 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
THE SOFTWARE.
|
24 |
+
-->
|
25 |
+
<config>
|
26 |
+
<modules>
|
27 |
+
<Wisepricer_JobQueue>
|
28 |
+
<version>1.0.0.0</version>
|
29 |
+
</Wisepricer_JobQueue>
|
30 |
+
</modules>
|
31 |
+
<global>
|
32 |
+
<models>
|
33 |
+
<jobqueue>
|
34 |
+
<class>Wisepricer_JobQueue_Model</class>
|
35 |
+
<resourceModel>jobqueue_resource</resourceModel>
|
36 |
+
</jobqueue>
|
37 |
+
<jobqueue_resource>
|
38 |
+
<class>Wisepricer_JobQueue_Model_Resource</class>
|
39 |
+
<entities>
|
40 |
+
<job>
|
41 |
+
<table>ws2_jobs</table>
|
42 |
+
</job>
|
43 |
+
</entities>
|
44 |
+
</jobqueue_resource>
|
45 |
+
</models>
|
46 |
+
<blocks>
|
47 |
+
<jobqueue>
|
48 |
+
<class>Wisepricer_JobQueue_Block</class>
|
49 |
+
</jobqueue>
|
50 |
+
</blocks>
|
51 |
+
<helpers>
|
52 |
+
<jobqueue>
|
53 |
+
<class>Wisepricer_JobQueue_Helper</class>
|
54 |
+
</jobqueue>
|
55 |
+
</helpers>
|
56 |
+
<resources>
|
57 |
+
<wisepricer_jobqueue_setup>
|
58 |
+
<setup>
|
59 |
+
<module>Wisepricer_JobQueue</module>
|
60 |
+
<class>Wisepricer_JobQueue_Model_Resource_Setup</class>
|
61 |
+
</setup>
|
62 |
+
</wisepricer_jobqueue_setup>
|
63 |
+
</resources>
|
64 |
+
</global>
|
65 |
+
<frontend>
|
66 |
+
<routers>
|
67 |
+
<wisepricer_jobqueue>
|
68 |
+
<use>standard</use>
|
69 |
+
<args>
|
70 |
+
<module>Wisepricer_JobQueue</module>
|
71 |
+
<frontName>jobqueue</frontName>
|
72 |
+
</args>
|
73 |
+
</wisepricer_jobqueue>
|
74 |
+
</routers>
|
75 |
+
</frontend>
|
76 |
+
<crontab>
|
77 |
+
<jobs>
|
78 |
+
<jobqueue_default>
|
79 |
+
<schedule>
|
80 |
+
<config_path>jobqueue/config/cron_expr</config_path>
|
81 |
+
</schedule>
|
82 |
+
<run>
|
83 |
+
<model>jobqueue/worker::executeJobs</model>
|
84 |
+
</run>
|
85 |
+
</jobqueue_default>
|
86 |
+
</jobs>
|
87 |
+
</crontab>
|
88 |
+
<default>
|
89 |
+
<jobqueue>
|
90 |
+
<config>
|
91 |
+
<enabled>1</enabled>
|
92 |
+
<cron_expr>* * * * *</cron_expr>
|
93 |
+
<max_attempts>3</max_attempts>
|
94 |
+
<queue>default</queue>
|
95 |
+
</config>
|
96 |
+
</jobqueue>
|
97 |
+
</default>
|
98 |
+
</config>
|
app/code/community/Wisepricer/JobQueue/sql/wisepricer_jobqueue_setup/mysql4-install-1.0.0.0.php
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The MIT License (MIT)
|
4 |
+
*
|
5 |
+
* Copyright (c) 2015 Jordan Owens <jkowens@gmail.com>
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*/
|
25 |
+
|
26 |
+
$installer = $this;
|
27 |
+
|
28 |
+
$installer->startSetup();
|
29 |
+
|
30 |
+
$installer->run(
|
31 |
+
"CREATE TABLE " . $installer->getTable('jobqueue/job')." (
|
32 |
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
33 |
+
`store_id` INT UNSIGNED NOT NULL DEFAULT 0,
|
34 |
+
`name` VARCHAR(255),
|
35 |
+
`handler` TEXT NOT NULL,
|
36 |
+
`queue` VARCHAR(255) NOT NULL DEFAULT 'default',
|
37 |
+
`attempts` INT UNSIGNED NOT NULL DEFAULT 0,
|
38 |
+
`run_at` DATETIME NULL,
|
39 |
+
`locked_at` DATETIME NULL,
|
40 |
+
`locked_by` VARCHAR(255) NULL,
|
41 |
+
`failed_at` DATETIME NULL,
|
42 |
+
`error` TEXT NULL,
|
43 |
+
`created_at` DATETIME NOT NULL
|
44 |
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
|
45 |
+
);
|
46 |
+
|
47 |
+
$installer->endSetup();
|
app/code/community/Wisepricer/Syncer2/Exception.php
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Wisepricer_Syncer2_Exception extends Mage_Core_Exception
|
4 |
+
{
|
5 |
+
|
6 |
+
}
|
app/code/community/Wisepricer/Syncer2/Helper/Data.php
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Wisepricer_Syncer2_Helper_Data extends Mage_Core_Helper_Abstract
|
4 |
+
{
|
5 |
+
const LOG_FILENAME = 'wisepricer_syncer2.log';
|
6 |
+
|
7 |
+
const PATH_VAR_WS2 = 'ws2';
|
8 |
+
const PATH_EXPORT = 'export';
|
9 |
+
const PATH_IMPORT = 'import';
|
10 |
+
|
11 |
+
const CONFIG_TOKEN = 'wisepricer_syncer2/authentication/token';
|
12 |
+
|
13 |
+
protected $_allAttributesCollection = null;
|
14 |
+
|
15 |
+
public function checkAttributeExists($attributeCode, $throwException = true)
|
16 |
+
{
|
17 |
+
if ($attributeCode) {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @var $_attribute Mage_Catalog_Model_Resource_Eav_Attribute
|
21 |
+
*/
|
22 |
+
foreach ($this->getAllAttributesCollection()->getItems() as $_attribute) {
|
23 |
+
if ($_attribute->getAttributeCode() === $attributeCode) {
|
24 |
+
return true;
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
}
|
29 |
+
|
30 |
+
if ($throwException) {
|
31 |
+
$this->exception(sprintf('Attribute with code "%s" does not exists', $attributeCode));
|
32 |
+
}
|
33 |
+
|
34 |
+
return false;
|
35 |
+
}
|
36 |
+
|
37 |
+
public function getAllAttributesCollection()
|
38 |
+
{
|
39 |
+
if( is_null($this->_allAttributesCollection) ) {
|
40 |
+
$this->_allAttributesCollection = Mage::getResourceModel('catalog/product_attribute_collection');
|
41 |
+
}
|
42 |
+
|
43 |
+
return $this->_allAttributesCollection;
|
44 |
+
}
|
45 |
+
|
46 |
+
public function getExportPath($baseFile = null)
|
47 |
+
{
|
48 |
+
return implode(DS, array(Mage::getBaseDir('var'), self::PATH_VAR_WS2, self::PATH_EXPORT, $baseFile));
|
49 |
+
}
|
50 |
+
|
51 |
+
public function getImportPath($baseFile = null)
|
52 |
+
{
|
53 |
+
return implode(DS, array(Mage::getBaseDir('var'), self::PATH_VAR_WS2, self::PATH_IMPORT, $baseFile));
|
54 |
+
}
|
55 |
+
|
56 |
+
public function getToken()
|
57 |
+
{
|
58 |
+
return Mage::getStoreConfig(self::CONFIG_TOKEN);
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @param mixed $message
|
63 |
+
* @param int $level
|
64 |
+
*/
|
65 |
+
public function log($message, $level = Zend_log::INFO)
|
66 |
+
{
|
67 |
+
$message = is_string($message) ? $message : print_r($message, true);
|
68 |
+
|
69 |
+
Mage::log($message, $level, self::LOG_FILENAME);
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* @param $message
|
74 |
+
* @param int $code
|
75 |
+
* @throws Mage_Core_Exception
|
76 |
+
*/
|
77 |
+
public function exception($message, $code = 0)
|
78 |
+
{
|
79 |
+
$this->log('Exception thrown: ' . $message, Zend_Log::CRIT);
|
80 |
+
throw Mage::exception('Wisepricer_Syncer2', $message, $code);
|
81 |
+
}
|
82 |
+
}
|
app/code/community/Wisepricer/Syncer2/Model/Abstract.php
ADDED
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
abstract class Wisepricer_Syncer2_Model_Abstract extends Wisepricer_JobQueue_Model_Job_Abstract
|
4 |
+
{
|
5 |
+
const REMOTE_SUCCESS_CODE = 1;
|
6 |
+
|
7 |
+
const REMOTE_FAILURE_CODE = 500;
|
8 |
+
|
9 |
+
public function perform()
|
10 |
+
{
|
11 |
+
Mage::app()->setCurrentStore($this->getStoreId());
|
12 |
+
}
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Enqueue job
|
16 |
+
*
|
17 |
+
* @param string $queue
|
18 |
+
* @param null $run_at
|
19 |
+
*/
|
20 |
+
public function enqueue($queue = "default", $run_at = null)
|
21 |
+
{
|
22 |
+
$this->validateNotRunning($queue);
|
23 |
+
|
24 |
+
$this->getHelper()->log(sprintf(
|
25 |
+
"Job '%s' (ID: '%s') added to queue. Data:\n%s",
|
26 |
+
get_class($this),
|
27 |
+
$this->getName(),
|
28 |
+
print_r($this->getData(), true)
|
29 |
+
));
|
30 |
+
|
31 |
+
parent::enqueue($queue, $run_at);
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* After max attempts running this method.
|
36 |
+
*
|
37 |
+
* @param $error
|
38 |
+
*/
|
39 |
+
public function _onDjjobRetryError($error)
|
40 |
+
{
|
41 |
+
$this->callbackRequest(self::REMOTE_FAILURE_CODE, $error);
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Validate that job not running now.
|
46 |
+
*
|
47 |
+
* @param $queue
|
48 |
+
* @throws Mage_Core_Exception
|
49 |
+
*/
|
50 |
+
protected function validateNotRunning($queue)
|
51 |
+
{
|
52 |
+
$collection = Mage::getModel('jobqueue/job')->getCollection();
|
53 |
+
$collection->addFieldToFilter('queue', array('eq' => $queue))
|
54 |
+
->addFieldToFilter('store_id', array('eq' => $this->getStoreId()))
|
55 |
+
->addFieldToFilter('failed_at', array('null' => true))
|
56 |
+
->addFieldToFilter('attempts', array('lt' => (int)Mage::getStoreConfig('jobqueue/config/max_attempts')));
|
57 |
+
|
58 |
+
if (count($collection->getItems()) > 0) {
|
59 |
+
$this->getHelper()->exception('Can\'t enqueue job, job running.');
|
60 |
+
}
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* @param int $code
|
65 |
+
* @param null $message
|
66 |
+
* @param array $data
|
67 |
+
* @return Zend_Http_Response
|
68 |
+
* @throws Exception
|
69 |
+
*/
|
70 |
+
protected function callbackRequest($code = self::REMOTE_SUCCESS_CODE, $message = null, $data = array())
|
71 |
+
{
|
72 |
+
$token = $this->getHelper()->getToken();
|
73 |
+
$defaults = array('token' => $token, 'job_id' => $this->getName(), 'magento_store_id' => $this->getStoreId());
|
74 |
+
$params = array('code' => $code, 'error_message' => $message) + $defaults + $data;
|
75 |
+
|
76 |
+
$this->getHelper()->log(sprintf(
|
77 |
+
"Making request to %s with params\n%s",
|
78 |
+
$this->getCallbackUrl(),
|
79 |
+
print_r($params, true)
|
80 |
+
));
|
81 |
+
|
82 |
+
try {
|
83 |
+
|
84 |
+
$httpClient = new Varien_Http_Client($this->getCallbackUrl());
|
85 |
+
$httpClient->setMethod(Varien_Http_Client::POST);
|
86 |
+
$httpClient->setParameterGet('token', $token);
|
87 |
+
$httpClient->setRawData(json_encode($params), 'application/json');
|
88 |
+
$httpClient->setHeaders(Varien_Http_Client::CONTENT_TYPE, 'application/json; charset=utf-8');
|
89 |
+
$httpClient->setHeaders(array('Accept' => 'application/json'));
|
90 |
+
|
91 |
+
$response = $httpClient->request();
|
92 |
+
|
93 |
+
} catch (Exception $e) {
|
94 |
+
$this->getHelper()->exception("Failed make callback request: " . $e->getMessage());
|
95 |
+
}
|
96 |
+
|
97 |
+
if ($response->isError()) {
|
98 |
+
$this->getHelper()->exception(
|
99 |
+
sprintf("Got error status '%s' and body: %s", $response->getStatus(), $response->getBody())
|
100 |
+
);
|
101 |
+
}
|
102 |
+
|
103 |
+
if ($jsonResponse = json_decode($response->getBody(), true)) {
|
104 |
+
|
105 |
+
if (empty($jsonResponse['code']) || $jsonResponse['code'] != 200) {
|
106 |
+
$this->getHelper()->exception(sprintf(
|
107 |
+
"Got error in response from callback server '%s'",
|
108 |
+
isset($jsonResponse['message']) ? $jsonResponse['message'] : 'EMPTY MESSAGE'
|
109 |
+
));
|
110 |
+
}
|
111 |
+
|
112 |
+
} else {
|
113 |
+
$this->getHelper()->exception(sprintf(
|
114 |
+
"Error on json decode. Error code: %s\nResponse: '%s'",
|
115 |
+
json_last_error(),
|
116 |
+
$response->getBody()
|
117 |
+
));
|
118 |
+
}
|
119 |
+
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* @return Wisepricer_Syncer2_Helper_Data
|
124 |
+
*/
|
125 |
+
protected function getHelper()
|
126 |
+
{
|
127 |
+
return Mage::helper('wisepricer_syncer2');
|
128 |
+
}
|
129 |
+
}
|
app/code/community/Wisepricer/Syncer2/Model/Exporter.php
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once(Mage::getModuleDir('', 'Wisepricer_Syncer2') . DS . 'Model' . DS . 'Abstract.php');
|
4 |
+
|
5 |
+
class Wisepricer_Syncer2_Model_Exporter extends Wisepricer_Syncer2_Model_Abstract
|
6 |
+
{
|
7 |
+
|
8 |
+
public function perform()
|
9 |
+
{
|
10 |
+
parent::perform();
|
11 |
+
|
12 |
+
try {
|
13 |
+
|
14 |
+
$fileName = $this->getName() . '.csv';
|
15 |
+
$csvFilePath = $this->getHelper()->getExportPath($fileName);
|
16 |
+
|
17 |
+
if (!file_exists($csvFilePath)) {
|
18 |
+
$this->prepareCsvFile($csvFilePath);
|
19 |
+
}
|
20 |
+
|
21 |
+
$this->callbackRequest(self::REMOTE_SUCCESS_CODE, null, array(
|
22 |
+
'file_url' => Mage::getUrl('wisesyncer2/api/export', array(
|
23 |
+
'token' => $this->getHelper()->getToken(),
|
24 |
+
'file' => $fileName,
|
25 |
+
))
|
26 |
+
));
|
27 |
+
|
28 |
+
} catch (Exception $e) {
|
29 |
+
$this->getHelper()->log("Failed on export: " . $e->getMessage());
|
30 |
+
throw $e;
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
protected function prepareCsvFile($filePath)
|
35 |
+
{
|
36 |
+
$this->getHelper()->log("Creating CSV file: $filePath");
|
37 |
+
|
38 |
+
$io = new Varien_Io_File();
|
39 |
+
$attributes = $this->getData('attributes');
|
40 |
+
$collection = $this->getFilteredCollection();
|
41 |
+
$temporaryFile = pathinfo($filePath, PATHINFO_FILENAME) . '.tmp';
|
42 |
+
|
43 |
+
$io->setAllowCreateFolders(true);
|
44 |
+
$io->open(array('path' => pathinfo($filePath, PATHINFO_DIRNAME)));
|
45 |
+
$io->streamOpen($temporaryFile, 'w+');
|
46 |
+
$io->streamLock(true);
|
47 |
+
|
48 |
+
if (!$io->streamWriteCsv($attributes)) {
|
49 |
+
$this->getHelper()->exception("Failed on writing headers to CSV file.");
|
50 |
+
}
|
51 |
+
|
52 |
+
//Add a page size to the result set.
|
53 |
+
$collection->setPageSize(100);
|
54 |
+
|
55 |
+
//discover how many page the result will be.
|
56 |
+
$pages = $collection->getLastPageNumber();
|
57 |
+
|
58 |
+
$currentPage = 1;
|
59 |
+
|
60 |
+
do {
|
61 |
+
|
62 |
+
$collection->setCurPage($currentPage);
|
63 |
+
$collection->load();
|
64 |
+
|
65 |
+
foreach ($collection as $item) {
|
66 |
+
$data = array();
|
67 |
+
|
68 |
+
foreach ($attributes as $attributeName) {
|
69 |
+
$data[] = $item->getData($attributeName);
|
70 |
+
}
|
71 |
+
|
72 |
+
if (!$io->streamWriteCsv($data)) {
|
73 |
+
$this->getHelper()->exception("Failed on writing data to CSV file.");
|
74 |
+
}
|
75 |
+
}
|
76 |
+
|
77 |
+
$currentPage++;
|
78 |
+
|
79 |
+
//make the collection unload the data in memory so it will pick up the next page when load() is called.
|
80 |
+
$collection->clear();
|
81 |
+
|
82 |
+
} while ($currentPage <= $pages);
|
83 |
+
|
84 |
+
$io->streamUnlock();
|
85 |
+
$io->streamClose();
|
86 |
+
|
87 |
+
if (!$io->mv($temporaryFile, $filePath)) {
|
88 |
+
$this->getHelper()->exception("Failed on moving $temporaryFile to $filePath");
|
89 |
+
}
|
90 |
+
|
91 |
+
$this->getHelper()->log("File writing finished.");
|
92 |
+
}
|
93 |
+
|
94 |
+
protected function getFilteredCollection()
|
95 |
+
{
|
96 |
+
$attributes = $this->getData('attributes');
|
97 |
+
$enabledOnly = $this->getData('enabled_only');
|
98 |
+
$productTypeFilter = $this->getData('product_type_filter');
|
99 |
+
$excludeAttribute = $this->getData('exclude_attribute');
|
100 |
+
|
101 |
+
/**
|
102 |
+
* @var $collection Mage_Catalog_Model_Resource_Product_Collection
|
103 |
+
*/
|
104 |
+
$collection = Mage::getModel('catalog/product')->getCollection()->addStoreFilter($this->getStoreId());
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Adding attributes to select, but validating each attribute. If the attribute is specified, but
|
108 |
+
* does not exists, we will get FATAL ERROR, the job will be interrupted, with no clue what happened.
|
109 |
+
*/
|
110 |
+
if (is_array($attributes) && !empty($attributes)) {
|
111 |
+
foreach ($attributes as $attributeCode) {
|
112 |
+
if ($this->getHelper()->checkAttributeExists($attributeCode)) {
|
113 |
+
$collection->addAttributeToSelect($attributeCode);
|
114 |
+
}
|
115 |
+
}
|
116 |
+
}
|
117 |
+
|
118 |
+
if ($enabledOnly) {
|
119 |
+
$collection->addFieldToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
|
120 |
+
}
|
121 |
+
|
122 |
+
if (is_array($productTypeFilter) && !empty($productTypeFilter)) {
|
123 |
+
$collection->addAttributeToFilter('type_id', array('in' => $productTypeFilter));
|
124 |
+
}
|
125 |
+
|
126 |
+
if ($excludeAttribute && $this->getHelper()->checkAttributeExists($excludeAttribute)) {
|
127 |
+
|
128 |
+
$collection->addAttributeToFilter(
|
129 |
+
array(
|
130 |
+
array('attribute' => $excludeAttribute, 'neq' => 1),
|
131 |
+
array('attribute' => $excludeAttribute, 'null' => true),
|
132 |
+
), '', 'left'
|
133 |
+
);
|
134 |
+
}
|
135 |
+
|
136 |
+
// die(print_r($collection->getSelect()->__toString(), true));
|
137 |
+
|
138 |
+
return $collection;
|
139 |
+
}
|
140 |
+
|
141 |
+
}
|
app/code/community/Wisepricer/Syncer2/Model/Importer.php
ADDED
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once(Mage::getModuleDir('', 'Wisepricer_Syncer2') . DS . 'Model' . DS . 'Abstract.php');
|
4 |
+
|
5 |
+
class Wisepricer_Syncer2_Model_Importer extends Wisepricer_Syncer2_Model_Abstract
|
6 |
+
{
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Downloads CSV file then running on it and repricing products.
|
10 |
+
*
|
11 |
+
* @throws Exception
|
12 |
+
*/
|
13 |
+
public function perform()
|
14 |
+
{
|
15 |
+
parent::perform();
|
16 |
+
|
17 |
+
try {
|
18 |
+
|
19 |
+
$data = array();
|
20 |
+
$fileName = $this->getName() . '.csv';
|
21 |
+
$localFile = $this->getHelper()->getImportPath($fileName);
|
22 |
+
|
23 |
+
if (!file_exists($localFile)) {
|
24 |
+
$this->downloadFile($this->getFileUrl(), $localFile);
|
25 |
+
}
|
26 |
+
|
27 |
+
$failedProducts = $this->performRepriceFromCSV($localFile);
|
28 |
+
|
29 |
+
if ($this->getReindexRequired()) {
|
30 |
+
Mage::getModel('index/indexer')->getProcessByCode('catalog_product_price')->reindexAll();
|
31 |
+
Mage::getModel('index/indexer')->getProcessByCode('catalog_product_attribute')->reindexAll();
|
32 |
+
}
|
33 |
+
|
34 |
+
if (!empty($failedProducts)) {
|
35 |
+
$data['failed_products'] = array();
|
36 |
+
|
37 |
+
foreach ($failedProducts as $sku => $product) {
|
38 |
+
$data['failed_products'][] = array(
|
39 |
+
'sku' => $sku,
|
40 |
+
'error_code' => 404,
|
41 |
+
'error_message' => 'Product not found.',
|
42 |
+
);
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
$this->callbackRequest(static::REMOTE_SUCCESS_CODE, null, $data);
|
47 |
+
|
48 |
+
} catch (Exception $e) {
|
49 |
+
$this->getHelper()->log("Failed on import: " . $e->getMessage());
|
50 |
+
throw $e;
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Download file from remote to local server.
|
56 |
+
*
|
57 |
+
* @param $remoteFile
|
58 |
+
* @param $localFile
|
59 |
+
* @throws Mage_Core_Exception
|
60 |
+
*/
|
61 |
+
protected function downloadFile($remoteFile, $localFile)
|
62 |
+
{
|
63 |
+
$this->getHelper()->log("Downloading file: $remoteFile");
|
64 |
+
|
65 |
+
$tmpFile = $localFile . '.tmp';
|
66 |
+
$tmpPath = pathinfo($tmpFile, PATHINFO_DIRNAME);
|
67 |
+
$remoteUrl = $remoteFile . '?' . http_build_query(array(
|
68 |
+
'token' => $this->getHelper()->getToken(),
|
69 |
+
'job_id' => $this->getName(),
|
70 |
+
'magento_store_id' => $this->getStoreId()
|
71 |
+
));
|
72 |
+
|
73 |
+
if (!file_exists($tmpPath) && !mkdir($tmpPath, 0777, true)) {
|
74 |
+
$this->getHelper()->exception("Can't create directory: $tmpPath. " . error_get_last());
|
75 |
+
}
|
76 |
+
|
77 |
+
if (false === ($fh = fopen($remoteUrl, 'r'))) {
|
78 |
+
$this->getHelper()->exception("Can't open stream: $remoteUrl. " . error_get_last());
|
79 |
+
}
|
80 |
+
|
81 |
+
if (!file_put_contents($tmpFile, $fh)) {
|
82 |
+
$this->getHelper()->exception("Can't write to file: $tmpFile. " . error_get_last());
|
83 |
+
}
|
84 |
+
|
85 |
+
if (!rename($tmpFile, $localFile)) {
|
86 |
+
$this->getHelper()->exception("Can't rename file $tmpFile to $localFile. " . error_get_last());
|
87 |
+
}
|
88 |
+
|
89 |
+
$this->getHelper()->log("File saved at: $localFile");
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Reprice products accodring to CSV
|
94 |
+
*
|
95 |
+
* @param $csvFile
|
96 |
+
* @throws Exception
|
97 |
+
*/
|
98 |
+
protected function performRepriceFromCSV($csvFile)
|
99 |
+
{
|
100 |
+
$this->getHelper()->log("Starting reprice.");
|
101 |
+
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
|
102 |
+
|
103 |
+
$productsData = $this->prepareProductsForReprice($csvFile);
|
104 |
+
$failedProducts = $productsData;
|
105 |
+
|
106 |
+
/**
|
107 |
+
* @var $product Mage_Catalog_Model_Product
|
108 |
+
*/
|
109 |
+
$product = Mage::getModel('catalog/product');
|
110 |
+
|
111 |
+
$connection = $product->getResource()->getWriteConnection();
|
112 |
+
$connection->beginTransaction();
|
113 |
+
try {
|
114 |
+
|
115 |
+
/**
|
116 |
+
* @var $collection Mage_Catalog_Model_Resource_Product_Collection
|
117 |
+
*/
|
118 |
+
$collection = $product->getCollection()
|
119 |
+
->setStoreId($this->getStoreId())
|
120 |
+
->addAttributeToFilter( 'sku', array( 'in' => array_keys($productsData)));
|
121 |
+
|
122 |
+
//Add a page size to the result set.
|
123 |
+
$collection->setPageSize(100);
|
124 |
+
|
125 |
+
//discover how many page the result will be.
|
126 |
+
$pages = $collection->getLastPageNumber();
|
127 |
+
|
128 |
+
$currentPage = 1;
|
129 |
+
|
130 |
+
do {
|
131 |
+
|
132 |
+
$collection->setCurPage($currentPage);
|
133 |
+
$collection->load();
|
134 |
+
|
135 |
+
/**
|
136 |
+
* @var $item Mage_Catalog_Model_Product
|
137 |
+
*/
|
138 |
+
foreach ($collection as $item) {
|
139 |
+
$sku = $item->getSku();
|
140 |
+
|
141 |
+
if (isset($productsData[$sku])) {
|
142 |
+
foreach ($productsData[$sku] as $attributeCode => $attributeValue) {
|
143 |
+
$item->setStoreId($this->getStoreId())
|
144 |
+
->setData($attributeCode, $attributeValue)
|
145 |
+
->getResource()
|
146 |
+
->saveAttribute($item, $attributeCode);
|
147 |
+
}
|
148 |
+
}
|
149 |
+
|
150 |
+
unset($failedProducts[$sku]);
|
151 |
+
}
|
152 |
+
|
153 |
+
$currentPage++;
|
154 |
+
|
155 |
+
//make the collection unload the data in memory so it will pick up the next page when load() is called.
|
156 |
+
$collection->clear();
|
157 |
+
|
158 |
+
} while ($currentPage <= $pages);
|
159 |
+
|
160 |
+
$connection->commit();
|
161 |
+
} catch (Exception $e) {
|
162 |
+
$connection->rollBack();
|
163 |
+
throw $e;
|
164 |
+
}
|
165 |
+
|
166 |
+
Mage::app()->setCurrentStore($this->getStoreId());
|
167 |
+
$this->getHelper()->log("Finished reprice.");
|
168 |
+
|
169 |
+
return $failedProducts;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Read csv file and prepares array for future reprice.
|
174 |
+
*
|
175 |
+
* @param $csvFile
|
176 |
+
* @return array
|
177 |
+
* @throws Exception
|
178 |
+
* @throws Mage_Core_Exception
|
179 |
+
*/
|
180 |
+
protected function prepareProductsForReprice($csvFile)
|
181 |
+
{
|
182 |
+
$this->getHelper()->log("Reading downloaded CSV file: $csvFile");
|
183 |
+
$products = array();
|
184 |
+
|
185 |
+
$io = new Varien_Io_File();
|
186 |
+
$io->streamOpen($csvFile, 'r');
|
187 |
+
|
188 |
+
$line = 1;
|
189 |
+
$attributes = $this->getAttributesFromCsvFile($io);
|
190 |
+
while ($csvData = $io->streamReadCsv()) {
|
191 |
+
$sku = $csvData[$attributes['sku']];
|
192 |
+
|
193 |
+
if (isset($products[$sku])) {
|
194 |
+
$io->streamClose();
|
195 |
+
$this->getHelper()->exception("Dublicate sku '$sku' found in ".basename($csvFile).":$line");
|
196 |
+
}
|
197 |
+
|
198 |
+
$data = array();
|
199 |
+
foreach ($attributes as $attributeCode => $index) {
|
200 |
+
if ($attributeCode === 'sku') {
|
201 |
+
continue;
|
202 |
+
}
|
203 |
+
$data[$attributeCode] = $csvData[$index];
|
204 |
+
}
|
205 |
+
|
206 |
+
$products[$sku] = $data;
|
207 |
+
|
208 |
+
$line++;
|
209 |
+
}
|
210 |
+
|
211 |
+
$io->streamClose();
|
212 |
+
$this->getHelper()->log("Finished reading downloaded CSV file: $csvFile");
|
213 |
+
|
214 |
+
return $products;
|
215 |
+
}
|
216 |
+
|
217 |
+
/**
|
218 |
+
* @param Varien_Io_File $io
|
219 |
+
* @return array
|
220 |
+
* @throws Wisepricer_Syncer2_Exception
|
221 |
+
*/
|
222 |
+
protected function getAttributesFromCsvFile(Varien_Io_File $io)
|
223 |
+
{
|
224 |
+
$attributes = array();
|
225 |
+
|
226 |
+
if (($headers = $io->streamReadCsv()) && !empty($headers)) {
|
227 |
+
foreach ($headers as $index => $attribute) {
|
228 |
+
$this->getHelper()->checkAttributeExists($attribute);
|
229 |
+
$attributes[$attribute] = $index;
|
230 |
+
//$attributes[$index] = $attribute;
|
231 |
+
}
|
232 |
+
} else {
|
233 |
+
$io->streamClose();
|
234 |
+
$this->getHelper()->exception('Can\'t read CSV headers from file.');
|
235 |
+
}
|
236 |
+
|
237 |
+
// if (array_search('sku', $attributes)) {
|
238 |
+
if (!isset($attributes['sku'])) {
|
239 |
+
$io->streamClose();
|
240 |
+
$this->getHelper()->exception('Missing "sku" in CSV headers.');
|
241 |
+
}
|
242 |
+
|
243 |
+
return $attributes;
|
244 |
+
}
|
245 |
+
|
246 |
+
}
|
app/code/community/Wisepricer/Syncer2/controllers/ApiController.php
ADDED
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class Wisepricer_Syncer2_ApiController extends Mage_Core_Controller_Front_Action
|
4 |
+
{
|
5 |
+
CONST RESPONSE_MESSAGE_SUCCESS = null;
|
6 |
+
|
7 |
+
CONST RESPONSE_MESSAGE_FAILURE = 'Failure';
|
8 |
+
|
9 |
+
CONST RESPONSE_MESSAGE_BADREQUEST = 'Wrong Request';
|
10 |
+
|
11 |
+
CONST RESPONSE_MESSAGE_TOKEN_MISS = 'Missing authorization token.';
|
12 |
+
|
13 |
+
CONST RESPONSE_MESSAGE_FORBIDDEN = 'Wrong authorization token.';
|
14 |
+
|
15 |
+
CONST RESPONSE_CODE_SUCCESS = 1;
|
16 |
+
|
17 |
+
CONST RESPONSE_CODE_FAILURE = 500;
|
18 |
+
|
19 |
+
CONST RESPONSE_CODE_BADREQUEST = 400;
|
20 |
+
|
21 |
+
CONST RESPONSE_CODE_TOKEN_MISS = 401;
|
22 |
+
|
23 |
+
CONST RESPONSE_CODE_FORBIDDEN = 403;
|
24 |
+
|
25 |
+
protected $_processInstance = null;
|
26 |
+
|
27 |
+
protected $jsonParams = null;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Token authorization
|
31 |
+
*/
|
32 |
+
public function preDispatch()
|
33 |
+
{
|
34 |
+
if ($token = $this->getParam('token')) {
|
35 |
+
|
36 |
+
if ($token !== $this->getHelper()->getToken()) {
|
37 |
+
$this->response(null, self::RESPONSE_CODE_FORBIDDEN, self::RESPONSE_MESSAGE_FORBIDDEN);
|
38 |
+
$this->setFlag('', self::FLAG_NO_DISPATCH, true);
|
39 |
+
}
|
40 |
+
|
41 |
+
} else {
|
42 |
+
$this->response(null, self::RESPONSE_CODE_TOKEN_MISS, self::RESPONSE_MESSAGE_TOKEN_MISS);
|
43 |
+
$this->setFlag('', self::FLAG_NO_DISPATCH, true);
|
44 |
+
}
|
45 |
+
|
46 |
+
parent::preDispatch();
|
47 |
+
return $this;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Retrieves the current website data, helps to check if the website is available
|
52 |
+
*/
|
53 |
+
public function pingAction()
|
54 |
+
{
|
55 |
+
try {
|
56 |
+
|
57 |
+
$website = Mage::app()->getWebsite();
|
58 |
+
|
59 |
+
$this->response(array(
|
60 |
+
'website_id' => $website->getCode(),
|
61 |
+
'website_name' => $website->getName(),
|
62 |
+
'website_url' => $website->getDefaultStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB),
|
63 |
+
));
|
64 |
+
|
65 |
+
} catch (Exception $e) {
|
66 |
+
$this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
|
67 |
+
}
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Retrieves the list of available websites data.
|
72 |
+
*/
|
73 |
+
public function getWebsitesAction()
|
74 |
+
{
|
75 |
+
$websites = array();
|
76 |
+
|
77 |
+
try {
|
78 |
+
|
79 |
+
/**
|
80 |
+
* @var $website Mage_Core_Model_Website
|
81 |
+
*/
|
82 |
+
foreach (Mage::app()->getWebsites() as $website) {
|
83 |
+
|
84 |
+
$stores = array();
|
85 |
+
|
86 |
+
/**
|
87 |
+
* @var $store Mage_Core_Model_Store
|
88 |
+
*/
|
89 |
+
foreach ($website->getStores() as $store) {
|
90 |
+
$stores[] = array(
|
91 |
+
'store_name' => $store->getGroup()->getName() . ' ' . $store->getName(),
|
92 |
+
'store_code' => $store->getCode(),
|
93 |
+
'store_id' => $store->getId(),
|
94 |
+
'store_url' => $store->getBaseUrl(),
|
95 |
+
);
|
96 |
+
}
|
97 |
+
|
98 |
+
$websites[] = array(
|
99 |
+
'website_name' => $website->getName(),
|
100 |
+
'website_code' => $website->getCode(),
|
101 |
+
'website_id' => $website->getId(),
|
102 |
+
'default_store' => $website->getDefaultStore()->getId(),
|
103 |
+
'stores' => $stores,
|
104 |
+
);
|
105 |
+
|
106 |
+
}
|
107 |
+
|
108 |
+
$this->response($websites);
|
109 |
+
|
110 |
+
} catch (Exception $e) {
|
111 |
+
$this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
/**
|
116 |
+
* Retrieves the list of product attributes available in the system
|
117 |
+
*/
|
118 |
+
public function getAttributesAction()
|
119 |
+
{
|
120 |
+
try {
|
121 |
+
|
122 |
+
$attributesCollection = Mage::getResourceModel('catalog/product_attribute_collection')
|
123 |
+
->setOrder('main_table.frontend_label', 'ASC')
|
124 |
+
->addFieldToFilter('main_table.frontend_label', array('neq' => 'NULL' ))
|
125 |
+
->getItems();
|
126 |
+
|
127 |
+
$data = array();
|
128 |
+
foreach ($attributesCollection as $attribute) {
|
129 |
+
$data[] = array(
|
130 |
+
'attribute_code' => $attribute->getAttributeCode(),
|
131 |
+
'attribute_label' => $attribute->getFrontendLabel(),
|
132 |
+
'attribute_type' => $attribute->getFrontendInput(),
|
133 |
+
'attribute_default_value' => $attribute->getDefaultValue(),
|
134 |
+
);
|
135 |
+
}
|
136 |
+
|
137 |
+
$this->response($data);
|
138 |
+
|
139 |
+
} catch (Exception $e) {
|
140 |
+
$this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
|
141 |
+
}
|
142 |
+
}
|
143 |
+
|
144 |
+
/**
|
145 |
+
* When called, processes the File Export scheduler or returns currents Status
|
146 |
+
* and current Progress in response;
|
147 |
+
*/
|
148 |
+
public function exportFileAction()
|
149 |
+
{
|
150 |
+
$errors = array();
|
151 |
+
$params = $this->getJsonParams();
|
152 |
+
|
153 |
+
if (!$this->getRequest()->isPost()) {
|
154 |
+
$errors[] = 'Request method should be POST.';
|
155 |
+
}
|
156 |
+
|
157 |
+
if (empty($params['store_id'])) {
|
158 |
+
$errors[] = "Param 'store_id' required and should be valid website ID.";
|
159 |
+
}
|
160 |
+
|
161 |
+
if (empty($params['attributes']) || !is_array($params['attributes'])) {
|
162 |
+
$errors[] = "Param 'attributes' required and should be not empty array.";
|
163 |
+
} else {
|
164 |
+
foreach ($params['attributes'] as $attribute) {
|
165 |
+
if (!$this->getHelper()->checkAttributeExists($attribute, false)) {
|
166 |
+
$errors[] = sprintf('Attribute with code "%s" does not exists', $attribute);
|
167 |
+
}
|
168 |
+
}
|
169 |
+
}
|
170 |
+
|
171 |
+
if (empty($params['callback_url'])) {
|
172 |
+
$errors[] = "Param 'callback_url' required.";
|
173 |
+
}
|
174 |
+
|
175 |
+
if (!empty($params['exclude_attribute']) && !$this->getHelper()->checkAttributeExists($params['exclude_attribute'], false)) {
|
176 |
+
$errors[] = sprintf('Exclude attribute with code "%s" does not exists', $params['exclude_attribute']);
|
177 |
+
}
|
178 |
+
|
179 |
+
if (!empty($params['product_type_filter'])) {
|
180 |
+
$productTypes = Mage_Catalog_Model_Product_Type::getTypes();
|
181 |
+
|
182 |
+
$productTypeFilter = explode(',', $params['product_type_filter']);
|
183 |
+
foreach ($productTypeFilter as $productType) {
|
184 |
+
if (!array_key_exists($productType, array_keys($productTypes))) {
|
185 |
+
$errors[] = sprintf("Product type '%s' not exists.", $productType);
|
186 |
+
}
|
187 |
+
}
|
188 |
+
}
|
189 |
+
|
190 |
+
if (empty($errors)) {
|
191 |
+
|
192 |
+
try {
|
193 |
+
|
194 |
+
$store = Mage::app()->getStore($params['store_id']);
|
195 |
+
$jobId = uniqid($store->getCode(), true) . '_export';
|
196 |
+
|
197 |
+
Mage::getModel('wisepricer_syncer2/exporter')
|
198 |
+
->setStoreId($store->getId())
|
199 |
+
->setName($jobId)
|
200 |
+
->addData(array(
|
201 |
+
'attributes' => $params['attributes'],
|
202 |
+
'callback_url' => $params['callback_url'],
|
203 |
+
'enabled_only' => !empty($params['enabled_only']) ? (bool) $params['enabled_only'] : null,
|
204 |
+
'product_type_filter' => !empty($params['product_type_filter']) ? explode(',', $params['product_type_filter']) : null,
|
205 |
+
'exclude_attribute' => !empty($params['exclude_attribute']) ? $params['exclude_attribute'] : null,
|
206 |
+
))
|
207 |
+
->enqueue();
|
208 |
+
|
209 |
+
$this->response(array('job_id' => $jobId));
|
210 |
+
|
211 |
+
} catch (Exception $e) {
|
212 |
+
$this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
|
213 |
+
}
|
214 |
+
|
215 |
+
} else {
|
216 |
+
$this->response(null, self::RESPONSE_CODE_BADREQUEST, implode(' ', $errors));
|
217 |
+
}
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* @throws Exception
|
222 |
+
* @throws Zend_Validate_Exception
|
223 |
+
*/
|
224 |
+
public function importFileAction()
|
225 |
+
{
|
226 |
+
$errors = array();
|
227 |
+
$params = $this->getJsonParams();
|
228 |
+
|
229 |
+
if (!$this->getRequest()->isPost()) {
|
230 |
+
$errors[] = 'Request method should be POST.';
|
231 |
+
}
|
232 |
+
|
233 |
+
if (empty($params['store_id'])) {
|
234 |
+
$errors[] = "Param 'store_id' required and should be valid website ID.";
|
235 |
+
}
|
236 |
+
|
237 |
+
if (empty($params['callback_url'])) {
|
238 |
+
$errors[] = "Param 'callback_url' required.";
|
239 |
+
}
|
240 |
+
|
241 |
+
if (empty($params['file_url'])) {
|
242 |
+
$errors[] = "Param 'file_url' required.";
|
243 |
+
}
|
244 |
+
|
245 |
+
if (empty($errors)) {
|
246 |
+
|
247 |
+
try {
|
248 |
+
|
249 |
+
$store = Mage::app()->getStore($params['store_id']);
|
250 |
+
$jobId = uniqid($store->getCode(), true) . '_import';
|
251 |
+
|
252 |
+
Mage::getModel('wisepricer_syncer2/importer')
|
253 |
+
->setStoreId($store->getId())
|
254 |
+
->setName($jobId)
|
255 |
+
->addData(array(
|
256 |
+
'file_url' => $params['file_url'],
|
257 |
+
'callback_url' => $params['callback_url'],
|
258 |
+
'reindex_required' => isset($params['reindex_required']) ? $params['reindex_required'] : true,
|
259 |
+
))
|
260 |
+
->enqueue();
|
261 |
+
|
262 |
+
$this->response(array('job_id' => $jobId));
|
263 |
+
|
264 |
+
} catch (Exception $e) {
|
265 |
+
$this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
|
266 |
+
}
|
267 |
+
|
268 |
+
} else {
|
269 |
+
$this->response(null, self::RESPONSE_CODE_BADREQUEST, implode(' ', $errors));
|
270 |
+
}
|
271 |
+
|
272 |
+
}
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Exports file for wiser
|
276 |
+
*/
|
277 |
+
public function exportAction()
|
278 |
+
{
|
279 |
+
if ($file = $this->getParam('file')) {
|
280 |
+
|
281 |
+
try {
|
282 |
+
|
283 |
+
$this->_prepareDownloadResponse(basename($file), array(
|
284 |
+
'type' => 'filename',
|
285 |
+
'value' => $this->getHelper()->getExportPath() . DS . basename($file),
|
286 |
+
));
|
287 |
+
|
288 |
+
|
289 |
+
} catch (Exception $e) {
|
290 |
+
$this->response(null, self::RESPONSE_CODE_FAILURE, $e->getMessage());
|
291 |
+
}
|
292 |
+
|
293 |
+
} else {
|
294 |
+
$this->response(null, self::RESPONSE_CODE_BADREQUEST, "Param 'file' can't be empty.");
|
295 |
+
}
|
296 |
+
}
|
297 |
+
|
298 |
+
/**
|
299 |
+
* Response with JSON encoded message.
|
300 |
+
* @param array $data Response data
|
301 |
+
* @param int $code Response status code
|
302 |
+
* @param string $response Response message
|
303 |
+
*/
|
304 |
+
protected function response($data = null, $code = self::RESPONSE_CODE_SUCCESS, $message = self::RESPONSE_MESSAGE_SUCCESS)
|
305 |
+
{
|
306 |
+
$this->getResponse()->setHeader('Content-type', 'application/json');
|
307 |
+
$this->getResponse()->setBody(json_encode(array(
|
308 |
+
'code' => $code,
|
309 |
+
'message' => $message,
|
310 |
+
'data' => $data,
|
311 |
+
)));
|
312 |
+
}
|
313 |
+
|
314 |
+
/**
|
315 |
+
* Return array from json request
|
316 |
+
* @return mixed|null
|
317 |
+
*/
|
318 |
+
protected function getJsonParams()
|
319 |
+
{
|
320 |
+
if ($this->jsonParams === null) {
|
321 |
+
$this->jsonParams = json_decode($this->getRequest()->getRawBody(), true);
|
322 |
+
}
|
323 |
+
|
324 |
+
return $this->jsonParams;
|
325 |
+
}
|
326 |
+
|
327 |
+
/**
|
328 |
+
* Get param from json request or from get request.
|
329 |
+
*
|
330 |
+
* @param $key
|
331 |
+
* @return mixed
|
332 |
+
*/
|
333 |
+
protected function getParam($key)
|
334 |
+
{
|
335 |
+
$params = $this->getJsonParams();
|
336 |
+
|
337 |
+
return isset($params[$key]) ? $params[$key] : $this->getRequest()->getParam($key, false);
|
338 |
+
}
|
339 |
+
|
340 |
+
/**
|
341 |
+
* @return Wisepricer_Syncer2_Helper_Data
|
342 |
+
*/
|
343 |
+
protected function getHelper()
|
344 |
+
{
|
345 |
+
return Mage::helper('wisepricer_syncer2');
|
346 |
+
}
|
347 |
+
|
348 |
+
}
|
app/code/community/Wisepricer/Syncer2/etc/adminhtml.xml
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<config>
|
3 |
+
<acl>
|
4 |
+
<resources>
|
5 |
+
<admin>
|
6 |
+
<children>
|
7 |
+
<system>
|
8 |
+
<children>
|
9 |
+
<config>
|
10 |
+
<children>
|
11 |
+
<wisepricer_syncer2>
|
12 |
+
<title>Wisepricer Syncer2 Configuration</title>
|
13 |
+
</wisepricer_syncer2>
|
14 |
+
</children>
|
15 |
+
</config>
|
16 |
+
</children>
|
17 |
+
</system>
|
18 |
+
</children>
|
19 |
+
</admin>
|
20 |
+
</resources>
|
21 |
+
</acl>
|
22 |
+
</config>
|
app/code/community/Wisepricer/Syncer2/etc/config.xml
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<config>
|
3 |
+
<modules>
|
4 |
+
<Wisepricer_Syncer2>
|
5 |
+
<version>2.0.0.0</version>
|
6 |
+
<modulename>Wisepricer Syncer2</modulename>
|
7 |
+
</Wisepricer_Syncer2>
|
8 |
+
</modules>
|
9 |
+
<global>
|
10 |
+
<models>
|
11 |
+
<wisepricer_syncer2>
|
12 |
+
<class>Wisepricer_Syncer2_Model</class>
|
13 |
+
<resourceModel>wisepricer_syncer2_mysql4</resourceModel>
|
14 |
+
</wisepricer_syncer2>
|
15 |
+
<wisepricer_syncer2_mysql4>
|
16 |
+
<class>Wisepricer_Syncer2_Model_Mysql4</class>
|
17 |
+
<entities>
|
18 |
+
<state>
|
19 |
+
<table>wisepricer_syncer2_state</table>
|
20 |
+
</state>
|
21 |
+
</entities>
|
22 |
+
</wisepricer_syncer2_mysql4>
|
23 |
+
</models>
|
24 |
+
<resources>
|
25 |
+
<wisepricer_syncer2_setup>
|
26 |
+
<setup>
|
27 |
+
<module>Wisepricer_Syncer2</module>
|
28 |
+
<class>Mage_Catalog_Model_Resource_Eav_Mysql4_Setup</class>
|
29 |
+
</setup>
|
30 |
+
<connection>
|
31 |
+
<use>core_setup</use>
|
32 |
+
</connection>
|
33 |
+
</wisepricer_syncer2_setup>
|
34 |
+
</resources>
|
35 |
+
<blocks>
|
36 |
+
<wisepricer_syncer2>
|
37 |
+
<class>Wisepricer_Syncer2_Block</class>
|
38 |
+
</wisepricer_syncer2>
|
39 |
+
</blocks>
|
40 |
+
<helpers>
|
41 |
+
<wisepricer_syncer2>
|
42 |
+
<class>Wisepricer_Syncer2_Helper</class>
|
43 |
+
</wisepricer_syncer2>
|
44 |
+
</helpers>
|
45 |
+
<events>
|
46 |
+
<wisepricer_syncer2_import_download_complete>
|
47 |
+
<observers>
|
48 |
+
<wisepricer_syncer2_observer>
|
49 |
+
<type>singleton</type>
|
50 |
+
<class>Wisepricer_Syncer2_Model_Observer</class>
|
51 |
+
<method>importDownloadComplete</method>
|
52 |
+
</wisepricer_syncer2_observer>
|
53 |
+
</observers>
|
54 |
+
</wisepricer_syncer2_import_download_complete>
|
55 |
+
</events>
|
56 |
+
</global>
|
57 |
+
<frontend>
|
58 |
+
<routers>
|
59 |
+
<wisepricer_syncer2>
|
60 |
+
<use>standard</use>
|
61 |
+
<args>
|
62 |
+
<module>Wisepricer_Syncer2</module>
|
63 |
+
<frontName>wisesyncer2</frontName>
|
64 |
+
</args>
|
65 |
+
</wisepricer_syncer2>
|
66 |
+
</routers>
|
67 |
+
</frontend>
|
68 |
+
<default>
|
69 |
+
<wisepricer_syncer2>
|
70 |
+
<authentication>
|
71 |
+
<token></token>
|
72 |
+
</authentication>
|
73 |
+
</wisepricer_syncer2>
|
74 |
+
</default>
|
75 |
+
</config>
|
app/code/community/Wisepricer/Syncer2/etc/system.xml
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<config>
|
3 |
+
<tabs>
|
4 |
+
<wisepricer_syncer2 translate="label" module="wisepricer_syncer2">
|
5 |
+
<!--label>Wisepricer Syncer2</label-->
|
6 |
+
<!--label><![CDATA[<img id="wisepricer_syncer2_block" src="" style="margin: 4px 0 0 -3px;" alt="" border="0" height="23" /> <script>$('wisepricer_syncer2_block').src = SKIN_URL + "../../default/default/images/wisepricer_syncer2/wiser_logo.png";</script>]]></label-->
|
7 |
+
<label><![CDATA[<div style="position: relative"><img id="wisepricer_syncer2_block" src="" style="position: absolute; left: -2px;" alt="" border="0" height="18" /></div> <script>$('wisepricer_syncer2_block').src = SKIN_URL + "../../default/default/images/wisepricer_syncer2/wiser_logo.png";</script>]]></label>
|
8 |
+
<sort_order>100</sort_order>
|
9 |
+
</wisepricer_syncer2>
|
10 |
+
</tabs>
|
11 |
+
<sections>
|
12 |
+
<wisepricer_syncer2 translate="label" module="wisepricer_syncer2">
|
13 |
+
<label>Options</label>
|
14 |
+
<tab>wisepricer_syncer2</tab>
|
15 |
+
<sort_order>1000</sort_order>
|
16 |
+
<show_in_default>1</show_in_default>
|
17 |
+
<show_in_website>0</show_in_website>
|
18 |
+
<show_in_store>0</show_in_store>
|
19 |
+
|
20 |
+
<groups>
|
21 |
+
<authentication translate="label" module="wisepricer_syncer2">
|
22 |
+
<label>Authentication Setup</label>
|
23 |
+
<comment><![CDATA[
|
24 |
+
<p>Hello,</p>
|
25 |
+
<p>This document is intended to be a guide to installing Wiser’s Magento Extension on your Magento Installation. This will detail the necessary steps on both the Wiser app as well as on the user’s Magento Installation. This guide assumes you have already downloaded and installed the Magento Extension from Magento Connect and installed it on your Magento Installation. If you are experiencing challenges in doing so please submit a ticket to <a href="mailto:support@wiser.com">support@wiser.com</a>.</p>
|
26 |
+
<p>
|
27 |
+
<ol type="1">
|
28 |
+
<li>1) The first step to integrating Magento to Wiser is to create a store in the Wiser app. Your CS representative can do this for you or if you feel comfortable enough in the app, you can do this on your own. This will generate an Authentication Key on Wiser’s app.</li>
|
29 |
+
<li>2) The second step is taking the Authentication Key generated from the Wiser app and pasting it in the Authentication Key field within the Wiser tab of your Magento Installation. The Authentication key field should be the first field you see in the Extension tab.</li>
|
30 |
+
<li>3) Once this is done, please notify your CS representative for the next steps in fully integrating your account. Thanks!</li>
|
31 |
+
</ol>
|
32 |
+
</p>
|
33 |
+
]]>
|
34 |
+
</comment>
|
35 |
+
<sort_order>10</sort_order>
|
36 |
+
<show_in_default>1</show_in_default>
|
37 |
+
<show_in_website>1</show_in_website>
|
38 |
+
<show_in_store>1</show_in_store>
|
39 |
+
<fields>
|
40 |
+
<token translate="label">
|
41 |
+
<label>Authentication Key: </label>
|
42 |
+
<comment><![CDATA[<span class="required">*</span>]]> Required</comment>
|
43 |
+
<frontend_type>text</frontend_type>
|
44 |
+
<sort_order>20</sort_order>
|
45 |
+
<show_in_default>1</show_in_default>
|
46 |
+
<show_in_website>1</show_in_website>
|
47 |
+
<show_in_store>1</show_in_store>
|
48 |
+
</token>
|
49 |
+
</fields>
|
50 |
+
</authentication>
|
51 |
+
|
52 |
+
</groups>
|
53 |
+
</wisepricer_syncer2>
|
54 |
+
</sections>
|
55 |
+
</config>
|
app/etc/modules/Wisepricer_Syncer2.xml
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<config>
|
3 |
+
<modules>
|
4 |
+
<Wisepricer_Syncer2>
|
5 |
+
<active>true</active>
|
6 |
+
<codePool>community</codePool>
|
7 |
+
<depends>
|
8 |
+
<Wisepricer_JobQueue/>
|
9 |
+
</depends>
|
10 |
+
</Wisepricer_Syncer2>
|
11 |
+
<Wisepricer_JobQueue>
|
12 |
+
<active>true</active>
|
13 |
+
<codePool>community</codePool>
|
14 |
+
</Wisepricer_JobQueue>
|
15 |
+
</modules>
|
16 |
+
</config>
|
lib/DJJob/DJJob.php
ADDED
@@ -0,0 +1,525 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
# This system is mostly a port of delayed_job: http://github.com/tobi/delayed_job
|
4 |
+
|
5 |
+
class DJException extends Exception { }
|
6 |
+
|
7 |
+
class DJRetryException extends DJException {
|
8 |
+
|
9 |
+
private $delay_seconds = 7200;
|
10 |
+
|
11 |
+
public function setDelay($delay) {
|
12 |
+
$this->delay_seconds = $delay;
|
13 |
+
}
|
14 |
+
public function getDelay() {
|
15 |
+
return $this->delay_seconds;
|
16 |
+
}
|
17 |
+
}
|
18 |
+
|
19 |
+
class DJBase {
|
20 |
+
|
21 |
+
// error severity levels
|
22 |
+
const CRITICAL = 4;
|
23 |
+
const ERROR = 3;
|
24 |
+
const WARN = 2;
|
25 |
+
const INFO = 1;
|
26 |
+
const DEBUG = 0;
|
27 |
+
|
28 |
+
private static $log_level = self::DEBUG;
|
29 |
+
|
30 |
+
private static $db = null;
|
31 |
+
protected static $jobsTable = "";
|
32 |
+
|
33 |
+
private static $dsn = "";
|
34 |
+
private static $user = "";
|
35 |
+
private static $password = "";
|
36 |
+
private static $retries = 3; //default retries
|
37 |
+
|
38 |
+
// use either `configure` or `setConnection`, depending on if
|
39 |
+
// you already have a PDO object you can re-use
|
40 |
+
|
41 |
+
public static function configure(){
|
42 |
+
$args = func_get_args();
|
43 |
+
$numArgs = func_num_args();
|
44 |
+
|
45 |
+
switch ($numArgs) {
|
46 |
+
case 1:{
|
47 |
+
if (is_array($args[0])){
|
48 |
+
self::configureWithOptions($args[0]);
|
49 |
+
} else {
|
50 |
+
self::configureWithDsnAndOptions($args[0]);
|
51 |
+
}
|
52 |
+
break;
|
53 |
+
}
|
54 |
+
case 2:{
|
55 |
+
if (is_array($args[0])){
|
56 |
+
self::configureWithOptions($args[0], $args[1]);
|
57 |
+
} else {
|
58 |
+
self::configureWithDsnAndOptions($args[0], $args[1]);
|
59 |
+
}
|
60 |
+
break;
|
61 |
+
}
|
62 |
+
case 3: {
|
63 |
+
self::configureWithDsnAndOptions($args[0], $args[1], $args[2]);
|
64 |
+
break;
|
65 |
+
}
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
protected static function configureWithDsnAndOptions($dsn, array $options = array(), $jobsTable = 'jobs') {
|
70 |
+
if (!isset($options['mysql_user'])){
|
71 |
+
throw new DJException("Please provide the database user in configure options array.");
|
72 |
+
}
|
73 |
+
if (!isset($options['mysql_pass'])){
|
74 |
+
throw new DJException("Please provide the database password in configure options array.");
|
75 |
+
}
|
76 |
+
|
77 |
+
self::$dsn = $dsn;
|
78 |
+
self::$jobsTable = $jobsTable;
|
79 |
+
|
80 |
+
self::$user = $options['mysql_user'];
|
81 |
+
self::$password = $options['mysql_pass'];
|
82 |
+
|
83 |
+
// searches for retries
|
84 |
+
if (isset($options['retries'])){
|
85 |
+
self::$retries = (int) $options['retries'];
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
protected static function configureWithOptions(array $options, $jobsTable = 'jobs') {
|
90 |
+
|
91 |
+
if (!isset($options['driver'])){
|
92 |
+
throw new DJException("Please provide the database driver used in configure options array.");
|
93 |
+
}
|
94 |
+
if (!isset($options['user'])){
|
95 |
+
throw new DJException("Please provide the database user in configure options array.");
|
96 |
+
}
|
97 |
+
if (!isset($options['password'])){
|
98 |
+
throw new DJException("Please provide the database password in configure options array.");
|
99 |
+
}
|
100 |
+
|
101 |
+
self::$user = $options['user'];
|
102 |
+
self::$password = $options['password'];
|
103 |
+
self::$jobsTable = $jobsTable;
|
104 |
+
|
105 |
+
self::$dsn = $options['driver'] . ':';
|
106 |
+
foreach ($options as $key => $value) {
|
107 |
+
// skips options already used
|
108 |
+
if ($key == 'driver' || $key == 'user' || $key == 'password') {
|
109 |
+
continue;
|
110 |
+
}
|
111 |
+
|
112 |
+
// searches for retries
|
113 |
+
if ($key == 'retries'){
|
114 |
+
self::$retries = (int) $value;
|
115 |
+
continue;
|
116 |
+
}
|
117 |
+
|
118 |
+
self::$dsn .= $key . '=' . $value . ';';
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
public static function setLogLevel($const) {
|
123 |
+
self::$log_level = $const;
|
124 |
+
}
|
125 |
+
|
126 |
+
public static function setConnection(PDO $db) {
|
127 |
+
self::$db = $db;
|
128 |
+
}
|
129 |
+
|
130 |
+
protected static function getConnection() {
|
131 |
+
if (self::$db === null) {
|
132 |
+
try {
|
133 |
+
self::$db = new PDO(self::$dsn, self::$user, self::$password);
|
134 |
+
self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
135 |
+
} catch (PDOException $e) {
|
136 |
+
throw new Exception("DJJob couldn't connect to the database. PDO said [{$e->getMessage()}]");
|
137 |
+
}
|
138 |
+
}
|
139 |
+
return self::$db;
|
140 |
+
}
|
141 |
+
|
142 |
+
public static function runQuery($sql, $params = array()) {
|
143 |
+
for ($attempts = 0; $attempts < self::$retries; $attempts++) {
|
144 |
+
try {
|
145 |
+
$stmt = self::getConnection()->prepare($sql);
|
146 |
+
$stmt->execute($params);
|
147 |
+
|
148 |
+
$ret = array();
|
149 |
+
if ($stmt->rowCount()) {
|
150 |
+
// calling fetchAll on a result set with no rows throws a
|
151 |
+
// "general error" exception
|
152 |
+
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) $ret []= $r;
|
153 |
+
}
|
154 |
+
|
155 |
+
$stmt->closeCursor();
|
156 |
+
return $ret;
|
157 |
+
}
|
158 |
+
catch (PDOException $e) {
|
159 |
+
// Catch "MySQL server has gone away" error.
|
160 |
+
if ($e->errorInfo[1] == 2006) {
|
161 |
+
self::$db = null;
|
162 |
+
}
|
163 |
+
// Throw all other errors as expected.
|
164 |
+
else {
|
165 |
+
throw $e;
|
166 |
+
}
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
throw new DJException("DJJob exhausted retries connecting to database");
|
171 |
+
}
|
172 |
+
|
173 |
+
public static function runUpdate($sql, $params = array()) {
|
174 |
+
for ($attempts = 0; $attempts < self::$retries; $attempts++) {
|
175 |
+
try {
|
176 |
+
$stmt = self::getConnection()->prepare($sql);
|
177 |
+
$stmt->execute($params);
|
178 |
+
return $stmt->rowCount();
|
179 |
+
}
|
180 |
+
catch (PDOException $e) {
|
181 |
+
// Catch "MySQL server has gone away" error.
|
182 |
+
if ($e->errorInfo[1] == 2006) {
|
183 |
+
self::$db = null;
|
184 |
+
}
|
185 |
+
// Throw all other errors as expected.
|
186 |
+
else {
|
187 |
+
throw $e;
|
188 |
+
}
|
189 |
+
}
|
190 |
+
}
|
191 |
+
|
192 |
+
throw new DJException("DJJob exhausted retries connecting to database");
|
193 |
+
}
|
194 |
+
|
195 |
+
protected static function log($mesg, $severity=self::CRITICAL) {
|
196 |
+
if ($severity >= self::$log_level) {
|
197 |
+
printf("[%s] %s\n", date('c'), $mesg);
|
198 |
+
}
|
199 |
+
}
|
200 |
+
}
|
201 |
+
|
202 |
+
class DJWorker extends DJBase {
|
203 |
+
# This is a singleton-ish thing. It wouldn't really make sense to
|
204 |
+
# instantiate more than one in a single request (or commandline task)
|
205 |
+
|
206 |
+
public function __construct($options = array()) {
|
207 |
+
$options = array_merge(array(
|
208 |
+
"queue" => "default",
|
209 |
+
"count" => 0,
|
210 |
+
"sleep" => 5,
|
211 |
+
"max_attempts" => 5,
|
212 |
+
"fail_on_output" => false
|
213 |
+
), $options);
|
214 |
+
list($this->queue, $this->count, $this->sleep, $this->max_attempts, $this->fail_on_output) =
|
215 |
+
array($options["queue"], $options["count"], $options["sleep"], $options["max_attempts"], $options["fail_on_output"]);
|
216 |
+
|
217 |
+
list($hostname, $pid) = array(trim(`hostname`), getmypid());
|
218 |
+
$this->name = "host::$hostname pid::$pid";
|
219 |
+
|
220 |
+
if (function_exists("pcntl_signal")) {
|
221 |
+
pcntl_signal(SIGTERM, array($this, "handleSignal"));
|
222 |
+
pcntl_signal(SIGINT, array($this, "handleSignal"));
|
223 |
+
}
|
224 |
+
}
|
225 |
+
|
226 |
+
public function handleSignal($signo) {
|
227 |
+
$signals = array(
|
228 |
+
SIGTERM => "SIGTERM",
|
229 |
+
SIGINT => "SIGINT"
|
230 |
+
);
|
231 |
+
$signal = $signals[$signo];
|
232 |
+
|
233 |
+
$this->log("[WORKER] Received received {$signal}... Shutting down", self::INFO);
|
234 |
+
$this->releaseLocks();
|
235 |
+
die(0);
|
236 |
+
}
|
237 |
+
|
238 |
+
public function releaseLocks() {
|
239 |
+
$this->runUpdate("
|
240 |
+
UPDATE " . self::$jobsTable . "
|
241 |
+
SET locked_at = NULL, locked_by = NULL
|
242 |
+
WHERE locked_by = ?",
|
243 |
+
array($this->name)
|
244 |
+
);
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Returns a new job ordered by most recent first
|
249 |
+
* why this?
|
250 |
+
* run newest first, some jobs get left behind
|
251 |
+
* run oldest first, all jobs get left behind
|
252 |
+
* @return DJJob
|
253 |
+
*/
|
254 |
+
public function getNewJob() {
|
255 |
+
# we can grab a locked job if we own the lock
|
256 |
+
$rs = $this->runQuery("
|
257 |
+
SELECT id
|
258 |
+
FROM " . self::$jobsTable . "
|
259 |
+
WHERE queue = ?
|
260 |
+
AND (run_at IS NULL OR NOW() >= run_at)
|
261 |
+
AND (locked_at IS NULL OR locked_by = ?)
|
262 |
+
AND failed_at IS NULL
|
263 |
+
AND attempts < ?
|
264 |
+
ORDER BY created_at DESC
|
265 |
+
LIMIT 10
|
266 |
+
", array($this->queue, $this->name, $this->max_attempts));
|
267 |
+
|
268 |
+
// randomly order the 10 to prevent lock contention among workers
|
269 |
+
shuffle($rs);
|
270 |
+
|
271 |
+
foreach ($rs as $r) {
|
272 |
+
$job = new DJJob($this->name, $r["id"], array(
|
273 |
+
"max_attempts" => $this->max_attempts,
|
274 |
+
"fail_on_output" => $this->fail_on_output
|
275 |
+
));
|
276 |
+
if ($job->acquireLock()) return $job;
|
277 |
+
}
|
278 |
+
|
279 |
+
return false;
|
280 |
+
}
|
281 |
+
|
282 |
+
public function start() {
|
283 |
+
$this->log("[JOB] Starting worker {$this->name} on queue::{$this->queue}", self::INFO);
|
284 |
+
|
285 |
+
$count = 0;
|
286 |
+
$job_count = 0;
|
287 |
+
try {
|
288 |
+
while ($this->count == 0 || $count < $this->count) {
|
289 |
+
if (function_exists("pcntl_signal_dispatch")) pcntl_signal_dispatch();
|
290 |
+
|
291 |
+
$count += 1;
|
292 |
+
$job = $this->getNewJob($this->queue);
|
293 |
+
|
294 |
+
if (!$job) {
|
295 |
+
$this->log("[JOB] Failed to get a job, queue::{$this->queue} may be empty", self::DEBUG);
|
296 |
+
sleep($this->sleep);
|
297 |
+
continue;
|
298 |
+
}
|
299 |
+
|
300 |
+
$job_count += 1;
|
301 |
+
$job->run();
|
302 |
+
}
|
303 |
+
} catch (Exception $e) {
|
304 |
+
$this->log("[JOB] unhandled exception::\"{$e->getMessage()}\"", self::ERROR);
|
305 |
+
}
|
306 |
+
|
307 |
+
$this->log("[JOB] worker shutting down after running {$job_count} jobs, over {$count} polling iterations", self::INFO);
|
308 |
+
}
|
309 |
+
}
|
310 |
+
|
311 |
+
class DJJob extends DJBase {
|
312 |
+
|
313 |
+
public function __construct($worker_name, $job_id, $options = array()) {
|
314 |
+
$options = array_merge(array(
|
315 |
+
"max_attempts" => 5,
|
316 |
+
"fail_on_output" => false
|
317 |
+
), $options);
|
318 |
+
$this->worker_name = $worker_name;
|
319 |
+
$this->job_id = $job_id;
|
320 |
+
$this->max_attempts = $options["max_attempts"];
|
321 |
+
$this->fail_on_output = $options["fail_on_output"];
|
322 |
+
}
|
323 |
+
|
324 |
+
public function run() {
|
325 |
+
# pull the handler from the db
|
326 |
+
$handler = $this->getHandler();
|
327 |
+
if (!is_object($handler)) {
|
328 |
+
$msg = "[JOB] bad handler for job::{$this->job_id}";
|
329 |
+
$this->finishWithError($msg);
|
330 |
+
return false;
|
331 |
+
}
|
332 |
+
|
333 |
+
# run the handler
|
334 |
+
try {
|
335 |
+
|
336 |
+
if ($this->fail_on_output) {
|
337 |
+
ob_start();
|
338 |
+
}
|
339 |
+
|
340 |
+
$handler->perform();
|
341 |
+
|
342 |
+
if ($this->fail_on_output) {
|
343 |
+
$output = ob_get_contents();
|
344 |
+
ob_end_clean();
|
345 |
+
|
346 |
+
if (!empty($output)) {
|
347 |
+
throw new Exception("Job produced unexpected output: $output");
|
348 |
+
}
|
349 |
+
}
|
350 |
+
|
351 |
+
# cleanup
|
352 |
+
$this->finish();
|
353 |
+
return true;
|
354 |
+
|
355 |
+
} catch (DJRetryException $e) {
|
356 |
+
# attempts hasn't been incremented yet.
|
357 |
+
$attempts = $this->getAttempts()+1;
|
358 |
+
|
359 |
+
$msg = "Caught DJRetryException \"{$e->getMessage()}\" on attempt $attempts/{$this->max_attempts}.";
|
360 |
+
|
361 |
+
if($attempts == $this->max_attempts) {
|
362 |
+
$msg = "[JOB] job::{$this->job_id} $msg Giving up.";
|
363 |
+
$this->finishWithError($msg, $handler);
|
364 |
+
} else {
|
365 |
+
$this->log("[JOB] job::{$this->job_id} $msg Try again in {$e->getDelay()} seconds.", self::WARN);
|
366 |
+
$this->retryLater($e->getDelay());
|
367 |
+
}
|
368 |
+
return false;
|
369 |
+
|
370 |
+
} catch (Exception $e) {
|
371 |
+
|
372 |
+
$this->finishWithError($e->getMessage(), $handler);
|
373 |
+
return false;
|
374 |
+
|
375 |
+
}
|
376 |
+
}
|
377 |
+
|
378 |
+
public function acquireLock() {
|
379 |
+
$this->log("[JOB] attempting to acquire lock for job::{$this->job_id} on {$this->worker_name}", self::INFO);
|
380 |
+
|
381 |
+
$lock = $this->runUpdate("
|
382 |
+
UPDATE " . self::$jobsTable . "
|
383 |
+
SET locked_at = NOW(), locked_by = ?
|
384 |
+
WHERE id = ? AND (locked_at IS NULL OR locked_by = ?) AND failed_at IS NULL
|
385 |
+
", array($this->worker_name, $this->job_id, $this->worker_name));
|
386 |
+
|
387 |
+
if (!$lock) {
|
388 |
+
$this->log("[JOB] failed to acquire lock for job::{$this->job_id}", self::INFO);
|
389 |
+
return false;
|
390 |
+
}
|
391 |
+
|
392 |
+
return true;
|
393 |
+
}
|
394 |
+
|
395 |
+
public function releaseLock() {
|
396 |
+
$this->runUpdate("
|
397 |
+
UPDATE " . self::$jobsTable . "
|
398 |
+
SET locked_at = NULL, locked_by = NULL
|
399 |
+
WHERE id = ?",
|
400 |
+
array($this->job_id)
|
401 |
+
);
|
402 |
+
}
|
403 |
+
|
404 |
+
public function finish() {
|
405 |
+
$this->runUpdate(
|
406 |
+
"DELETE FROM " . self::$jobsTable . " WHERE id = ?",
|
407 |
+
array($this->job_id)
|
408 |
+
);
|
409 |
+
$this->log("[JOB] completed job::{$this->job_id}", self::INFO);
|
410 |
+
}
|
411 |
+
|
412 |
+
public function finishWithError($error, $handler = null) {
|
413 |
+
$this->runUpdate("
|
414 |
+
UPDATE " . self::$jobsTable . "
|
415 |
+
SET attempts = attempts + 1,
|
416 |
+
failed_at = IF(attempts >= ?, NOW(), NULL),
|
417 |
+
error = IF(attempts >= ?, ?, NULL)
|
418 |
+
WHERE id = ?",
|
419 |
+
array(
|
420 |
+
$this->max_attempts,
|
421 |
+
$this->max_attempts,
|
422 |
+
$error,
|
423 |
+
$this->job_id
|
424 |
+
)
|
425 |
+
);
|
426 |
+
$this->log($error, self::ERROR);
|
427 |
+
$this->log("[JOB] failure in job::{$this->job_id}", self::ERROR);
|
428 |
+
$this->releaseLock();
|
429 |
+
|
430 |
+
if ($handler && ($this->getAttempts() == $this->max_attempts) && method_exists($handler, '_onDjjobRetryError')) {
|
431 |
+
$handler->_onDjjobRetryError($error);
|
432 |
+
}
|
433 |
+
}
|
434 |
+
|
435 |
+
public function retryLater($delay) {
|
436 |
+
$this->runUpdate("
|
437 |
+
UPDATE " . self::$jobsTable . "
|
438 |
+
SET run_at = DATE_ADD(NOW(), INTERVAL ? SECOND),
|
439 |
+
attempts = attempts + 1
|
440 |
+
WHERE id = ?",
|
441 |
+
array(
|
442 |
+
$delay,
|
443 |
+
$this->job_id
|
444 |
+
)
|
445 |
+
);
|
446 |
+
$this->releaseLock();
|
447 |
+
}
|
448 |
+
|
449 |
+
public function getHandler() {
|
450 |
+
$rs = $this->runQuery(
|
451 |
+
"SELECT handler FROM " . self::$jobsTable . " WHERE id = ?",
|
452 |
+
array($this->job_id)
|
453 |
+
);
|
454 |
+
foreach ($rs as $r) return unserialize($r["handler"]);
|
455 |
+
return false;
|
456 |
+
}
|
457 |
+
|
458 |
+
public function getAttempts() {
|
459 |
+
$rs = $this->runQuery(
|
460 |
+
"SELECT attempts FROM " . self::$jobsTable . " WHERE id = ?",
|
461 |
+
array($this->job_id)
|
462 |
+
);
|
463 |
+
foreach ($rs as $r) return $r["attempts"];
|
464 |
+
return false;
|
465 |
+
}
|
466 |
+
|
467 |
+
public static function enqueue($handler, $queue = "default", $run_at = null) {
|
468 |
+
$affected = self::runUpdate(
|
469 |
+
"INSERT INTO " . self::$jobsTable . " (handler, queue, run_at, created_at) VALUES(?, ?, ?, NOW())",
|
470 |
+
array(serialize($handler), (string) $queue, $run_at)
|
471 |
+
);
|
472 |
+
|
473 |
+
if ($affected < 1) {
|
474 |
+
self::log("[JOB] failed to enqueue new job", self::ERROR);
|
475 |
+
return false;
|
476 |
+
}
|
477 |
+
|
478 |
+
return self::getConnection()->lastInsertId(); // return the job ID, for manipulation later
|
479 |
+
}
|
480 |
+
|
481 |
+
public static function bulkEnqueue($handlers, $queue = "default", $run_at = null) {
|
482 |
+
$sql = "INSERT INTO " . self::$jobsTable . " (handler, queue, run_at, created_at) VALUES";
|
483 |
+
$sql .= implode(",", array_fill(0, count($handlers), "(?, ?, ?, NOW())"));
|
484 |
+
|
485 |
+
$parameters = array();
|
486 |
+
foreach ($handlers as $handler) {
|
487 |
+
$parameters []= serialize($handler);
|
488 |
+
$parameters []= (string) $queue;
|
489 |
+
$parameters []= $run_at;
|
490 |
+
}
|
491 |
+
$affected = self::runUpdate($sql, $parameters);
|
492 |
+
|
493 |
+
if ($affected < 1) {
|
494 |
+
self::log("[JOB] failed to enqueue new jobs", self::ERROR);
|
495 |
+
return false;
|
496 |
+
}
|
497 |
+
|
498 |
+
if ($affected != count($handlers))
|
499 |
+
self::log("[JOB] failed to enqueue some new jobs", self::ERROR);
|
500 |
+
|
501 |
+
return true;
|
502 |
+
}
|
503 |
+
|
504 |
+
public static function status($queue = "default") {
|
505 |
+
$rs = self::runQuery("
|
506 |
+
SELECT COUNT(*) as total, COUNT(failed_at) as failed, COUNT(locked_at) as locked
|
507 |
+
FROM `" . self::$jobsTable . "`
|
508 |
+
WHERE queue = ?
|
509 |
+
", array($queue));
|
510 |
+
$rs = $rs[0];
|
511 |
+
|
512 |
+
$failed = $rs["failed"];
|
513 |
+
$locked = $rs["locked"];
|
514 |
+
$total = $rs["total"];
|
515 |
+
$outstanding = $total - $locked - $failed;
|
516 |
+
|
517 |
+
return array(
|
518 |
+
"outstanding" => $outstanding,
|
519 |
+
"locked" => $locked,
|
520 |
+
"failed" => $failed,
|
521 |
+
"total" => $total
|
522 |
+
);
|
523 |
+
}
|
524 |
+
|
525 |
+
}
|
lib/DJJob/examples/HelloWorldJob.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class HelloWorldJob {
|
4 |
+
|
5 |
+
public function __construct($name) {
|
6 |
+
$this->name = $name;
|
7 |
+
}
|
8 |
+
|
9 |
+
public function perform() {
|
10 |
+
echo "Hello {$this->name}!\n";
|
11 |
+
}
|
12 |
+
|
13 |
+
}
|
lib/DJJob/jobs.sql
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
CREATE TABLE `jobs` (
|
2 |
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
3 |
+
`handler` TEXT NOT NULL,
|
4 |
+
`queue` VARCHAR(255) NOT NULL DEFAULT 'default',
|
5 |
+
`attempts` INT UNSIGNED NOT NULL DEFAULT 0,
|
6 |
+
`run_at` DATETIME NULL,
|
7 |
+
`locked_at` DATETIME NULL,
|
8 |
+
`locked_by` VARCHAR(255) NULL,
|
9 |
+
`failed_at` DATETIME NULL,
|
10 |
+
`error` TEXT NULL,
|
11 |
+
`created_at` DATETIME NOT NULL
|
12 |
+
) ENGINE = INNODB;
|
lib/DJJob/test/custom_table_name.php
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function assert_handler($file, $line, $code, $desc = null) {
|
4 |
+
printf("Assertion failed at %s:%s: %s: %s\n", $file, $line, $code, $desc);
|
5 |
+
}
|
6 |
+
|
7 |
+
assert_options(ASSERT_ACTIVE, 1);
|
8 |
+
assert_options(ASSERT_WARNING, 0);
|
9 |
+
assert_options(ASSERT_QUIET_EVAL, 1);
|
10 |
+
assert_options(ASSERT_CALLBACK, 'assert_handler');
|
11 |
+
|
12 |
+
date_default_timezone_set('America/New_York');
|
13 |
+
|
14 |
+
require dirname(__FILE__) . "/../DJJob.php";
|
15 |
+
|
16 |
+
DJJob::configure([
|
17 |
+
'driver' => 'mysql',
|
18 |
+
'host' => '127.0.0.1',
|
19 |
+
'dbname' => 'djjob',
|
20 |
+
'user' => 'root',
|
21 |
+
'password' => 'root',
|
22 |
+
], 'my_jobs');
|
23 |
+
|
24 |
+
DJJob::runQuery("
|
25 |
+
DROP TABLE IF EXISTS `my_jobs`;
|
26 |
+
CREATE TABLE `my_jobs` (
|
27 |
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
28 |
+
`handler` VARCHAR(255) NOT NULL,
|
29 |
+
`queue` VARCHAR(255) NOT NULL DEFAULT 'default',
|
30 |
+
`attempts` INT UNSIGNED NOT NULL DEFAULT 0,
|
31 |
+
`run_at` DATETIME NULL,
|
32 |
+
`locked_at` DATETIME NULL,
|
33 |
+
`locked_by` VARCHAR(255) NULL,
|
34 |
+
`failed_at` DATETIME NULL,
|
35 |
+
`error` VARCHAR(255) NULL,
|
36 |
+
`created_at` DATETIME NOT NULL
|
37 |
+
) ENGINE = MEMORY;
|
38 |
+
");
|
39 |
+
|
40 |
+
class HelloWorldJob {
|
41 |
+
public function __construct($name) {
|
42 |
+
$this->name = $name;
|
43 |
+
}
|
44 |
+
public function perform() {
|
45 |
+
echo "Hello {$this->name}!\n";
|
46 |
+
sleep(1);
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
class FailingJob {
|
51 |
+
public function perform() {
|
52 |
+
sleep(1);
|
53 |
+
throw new Exception("Uh oh");
|
54 |
+
}
|
55 |
+
}
|
56 |
+
|
57 |
+
$status = DJJob::status();
|
58 |
+
|
59 |
+
assert('$status["outstanding"] == 0', "Initial outstanding status is incorrect");
|
60 |
+
assert('$status["locked"] == 0', "Initial locked status is incorrect");
|
61 |
+
assert('$status["failed"] == 0', "Initial failed status is incorrect");
|
62 |
+
assert('$status["total"] == 0', "Initial total status is incorrect");
|
63 |
+
|
64 |
+
printf("=====================\nStarting run of DJJob\n=====================\n\n");
|
65 |
+
|
66 |
+
DJJob::enqueue(new HelloWorldJob("delayed_job"));
|
67 |
+
DJJob::bulkEnqueue(array(
|
68 |
+
new HelloWorldJob("shopify"),
|
69 |
+
new HelloWorldJob("github"),
|
70 |
+
));
|
71 |
+
DJJob::enqueue(new FailingJob());
|
72 |
+
|
73 |
+
$worker = new DJWorker(array("count" => 5, "max_attempts" => 2, "sleep" => 10));
|
74 |
+
$worker->start();
|
75 |
+
printf("\n============\nRun complete\n============\n\n");
|
76 |
+
|
77 |
+
$status = DJJob::status();
|
78 |
+
|
79 |
+
assert('$status["outstanding"] == 0', "Final outstanding status is incorrect");
|
80 |
+
assert('$status["locked"] == 0', "Final locked status is incorrect");
|
81 |
+
assert('$status["failed"] == 1', "Final failed status is incorrect");
|
82 |
+
assert('$status["total"] == 1', "Final total status is incorrect");
|
lib/DJJob/test/database.php
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function assert_handler($file, $line, $code, $desc = null) {
|
4 |
+
printf("Assertion failed at %s:%s: %s: %s\n", $file, $line, $code, $desc);
|
5 |
+
}
|
6 |
+
|
7 |
+
assert_options(ASSERT_ACTIVE, 1);
|
8 |
+
assert_options(ASSERT_WARNING, 0);
|
9 |
+
assert_options(ASSERT_QUIET_EVAL, 1);
|
10 |
+
assert_options(ASSERT_CALLBACK, 'assert_handler');
|
11 |
+
|
12 |
+
date_default_timezone_set('America/New_York');
|
13 |
+
|
14 |
+
require dirname(__FILE__) . "/../DJJob.php";
|
15 |
+
|
16 |
+
DJJob::configure([
|
17 |
+
'driver' => 'mysql',
|
18 |
+
'host' => '127.0.0.1',
|
19 |
+
'dbname' => 'djjob',
|
20 |
+
'user' => 'root',
|
21 |
+
'password' => 'root',
|
22 |
+
]);
|
23 |
+
|
24 |
+
DJJob::runQuery("
|
25 |
+
DROP TABLE IF EXISTS `jobs`;
|
26 |
+
CREATE TABLE `jobs` (
|
27 |
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
28 |
+
`handler` VARCHAR(255) NOT NULL,
|
29 |
+
`queue` VARCHAR(255) NOT NULL DEFAULT 'default',
|
30 |
+
`attempts` INT UNSIGNED NOT NULL DEFAULT 0,
|
31 |
+
`run_at` DATETIME NULL,
|
32 |
+
`locked_at` DATETIME NULL,
|
33 |
+
`locked_by` VARCHAR(255) NULL,
|
34 |
+
`failed_at` DATETIME NULL,
|
35 |
+
`error` VARCHAR(255) NULL,
|
36 |
+
`created_at` DATETIME NOT NULL
|
37 |
+
) ENGINE = MEMORY;
|
38 |
+
");
|
39 |
+
|
40 |
+
class HelloWorldJob {
|
41 |
+
public function __construct($name) {
|
42 |
+
$this->name = $name;
|
43 |
+
}
|
44 |
+
public function perform() {
|
45 |
+
echo "Hello {$this->name}!\n";
|
46 |
+
sleep(1);
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
class FailingJob {
|
51 |
+
public function perform() {
|
52 |
+
sleep(1);
|
53 |
+
throw new Exception("Uh oh");
|
54 |
+
}
|
55 |
+
}
|
56 |
+
|
57 |
+
$status = DJJob::status();
|
58 |
+
|
59 |
+
assert('$status["outstanding"] == 0', "Initial outstanding status is incorrect");
|
60 |
+
assert('$status["locked"] == 0', "Initial locked status is incorrect");
|
61 |
+
assert('$status["failed"] == 0', "Initial failed status is incorrect");
|
62 |
+
assert('$status["total"] == 0', "Initial total status is incorrect");
|
63 |
+
|
64 |
+
printf("=====================\nStarting run of DJJob\n=====================\n\n");
|
65 |
+
|
66 |
+
DJJob::enqueue(new HelloWorldJob("delayed_job"));
|
67 |
+
DJJob::bulkEnqueue(array(
|
68 |
+
new HelloWorldJob("shopify"),
|
69 |
+
new HelloWorldJob("github"),
|
70 |
+
));
|
71 |
+
DJJob::enqueue(new FailingJob());
|
72 |
+
// Test unicode support using the classic, rails snowman: http://www.fileformat.info/info/unicode/char/2603/browsertest.htm
|
73 |
+
DJJob::enqueue(new HelloWorldJob(html_entity_decode("☃", ENT_HTML5, "UTF-8")));
|
74 |
+
|
75 |
+
$worker = new DJWorker(array("count" => 5, "max_attempts" => 2, "sleep" => 10));
|
76 |
+
$worker->start();
|
77 |
+
printf("\n============\nRun complete\n============\n\n");
|
78 |
+
|
79 |
+
$status = DJJob::status();
|
80 |
+
|
81 |
+
assert('$status["outstanding"] == 0', "Final outstanding status is incorrect");
|
82 |
+
assert('$status["locked"] == 0', "Final locked status is incorrect");
|
83 |
+
assert('$status["failed"] == 1', "Final failed status is incorrect");
|
84 |
+
assert('$status["total"] == 1', "Final total status is incorrect");
|
lib/DJJob/test/original_database_configure.php
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function assert_handler($file, $line, $code, $desc = null) {
|
4 |
+
printf("Assertion failed at %s:%s: %s: %s\n", $file, $line, $code, $desc);
|
5 |
+
}
|
6 |
+
|
7 |
+
assert_options(ASSERT_ACTIVE, 1);
|
8 |
+
assert_options(ASSERT_WARNING, 0);
|
9 |
+
assert_options(ASSERT_QUIET_EVAL, 1);
|
10 |
+
assert_options(ASSERT_CALLBACK, 'assert_handler');
|
11 |
+
|
12 |
+
date_default_timezone_set('America/New_York');
|
13 |
+
|
14 |
+
require dirname(__FILE__) . "/../DJJob.php";
|
15 |
+
|
16 |
+
DJJob::configure("mysql:host=127.0.0.1;dbname=djjob;", array(
|
17 |
+
"mysql_user" => "root",
|
18 |
+
"mysql_pass" => "root",
|
19 |
+
));
|
20 |
+
|
21 |
+
DJJob::runQuery("
|
22 |
+
DROP TABLE IF EXISTS `jobs`;
|
23 |
+
CREATE TABLE `jobs` (
|
24 |
+
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
25 |
+
`handler` VARCHAR(255) NOT NULL,
|
26 |
+
`queue` VARCHAR(255) NOT NULL DEFAULT 'default',
|
27 |
+
`attempts` INT UNSIGNED NOT NULL DEFAULT 0,
|
28 |
+
`run_at` DATETIME NULL,
|
29 |
+
`locked_at` DATETIME NULL,
|
30 |
+
`locked_by` VARCHAR(255) NULL,
|
31 |
+
`failed_at` DATETIME NULL,
|
32 |
+
`error` VARCHAR(255) NULL,
|
33 |
+
`created_at` DATETIME NOT NULL
|
34 |
+
) ENGINE = MEMORY;
|
35 |
+
");
|
36 |
+
|
37 |
+
class HelloWorldJob {
|
38 |
+
public function __construct($name) {
|
39 |
+
$this->name = $name;
|
40 |
+
}
|
41 |
+
public function perform() {
|
42 |
+
echo "Hello {$this->name}!\n";
|
43 |
+
sleep(1);
|
44 |
+
}
|
45 |
+
}
|
46 |
+
|
47 |
+
class FailingJob {
|
48 |
+
public function perform() {
|
49 |
+
sleep(1);
|
50 |
+
throw new Exception("Uh oh");
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
$status = DJJob::status();
|
55 |
+
|
56 |
+
assert('$status["outstanding"] == 0', "Initial outstanding status is incorrect");
|
57 |
+
assert('$status["locked"] == 0', "Initial locked status is incorrect");
|
58 |
+
assert('$status["failed"] == 0', "Initial failed status is incorrect");
|
59 |
+
assert('$status["total"] == 0', "Initial total status is incorrect");
|
60 |
+
|
61 |
+
printf("=====================\nStarting run of DJJob\n=====================\n\n");
|
62 |
+
|
63 |
+
DJJob::enqueue(new HelloWorldJob("delayed_job"));
|
64 |
+
DJJob::bulkEnqueue(array(
|
65 |
+
new HelloWorldJob("shopify"),
|
66 |
+
new HelloWorldJob("github"),
|
67 |
+
));
|
68 |
+
DJJob::enqueue(new FailingJob());
|
69 |
+
|
70 |
+
$worker = new DJWorker(array("count" => 5, "max_attempts" => 2, "sleep" => 10));
|
71 |
+
$worker->start();
|
72 |
+
printf("\n============\nRun complete\n============\n\n");
|
73 |
+
|
74 |
+
$status = DJJob::status();
|
75 |
+
|
76 |
+
assert('$status["outstanding"] == 0', "Final outstanding status is incorrect");
|
77 |
+
assert('$status["locked"] == 0', "Final locked status is incorrect");
|
78 |
+
assert('$status["failed"] == 1', "Final failed status is incorrect");
|
79 |
+
assert('$status["total"] == 1', "Final total status is incorrect");
|
package.xml
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0"?>
|
2 |
+
<package>
|
3 |
+
<name>Wisepricer_Syncer2</name>
|
4 |
+
<version>2.0.0.0</version>
|
5 |
+
<stability>stable</stability>
|
6 |
+
<license uri="http://www.opensource.org/licenses/osl-3.0.php">OSL</license>
|
7 |
+
<channel>community</channel>
|
8 |
+
<extends/>
|
9 |
+
<summary>WisePricer - Beat your competition</summary>
|
10 |
+
<description>WisePricer is a new tool that allows you to Track & monitor successful online retailers and update your store prices in real-time. With WisePricer you'll never get left behind the competition.</description>
|
11 |
+
<notes>First stable release.</notes>
|
12 |
+
<authors><author><name>Pavel Zhytomirsky</name><user>r3volut1oner</user><email>r3volut1oner@gmail.com</email></author></authors>
|
13 |
+
<date>2015-09-02</date>
|
14 |
+
<time>10:13:46</time>
|
15 |
+
<contents><target name="magecommunity"><dir name="Wisepricer"><dir name="JobQueue"><dir name="Block"><dir name="Adminhtml"><dir name="Job"><file name="View.php" hash="aae12860e5c8c4b58f6d22372905ee7a"/></dir><dir name="Queue"><file name="Grid.php" hash="901021bad6652b9d4df6477fe893154a"/></dir><file name="Queue.php" hash="6f91a2d61d08d50fdf8c45c8857469be"/></dir></dir><dir name="Helper"><file name="Data.php" hash="055ae3239bd12f249a04ff46c27715d4"/></dir><dir name="Model"><dir name="Job"><file name="Abstract.php" hash="424f8974635068988567d637ab336d37"/></dir><file name="Job.php" hash="70aa35a24b5a972385187906cd01cf82"/><dir name="Resource"><dir name="Job"><file name="Collection.php" hash="69378351d91089d7c3e5f21c44c85ef2"/></dir><file name="Job.php" hash="9aa3bd32786cdf3d8a999d8c2fe1790e"/><file name="Setup.php" hash="c44ec3d6eed166951904d5ca7b654e40"/></dir><file name="Worker.php" hash="6b1e043cdc802851eecca990feb84f8f"/></dir><dir name="controllers"><dir name="Adminhtml"><file name="QueueController.php" hash="13e4a6e275dd92d8f6bfa4aef2860124"/></dir></dir><dir name="etc"><file name="config.xml" hash="cf1ed321c554546028181d31abf374e4"/></dir><dir name="sql"><dir name="wisepricer_jobqueue_setup"><file name="mysql4-install-1.0.0.0.php" hash="a6a1b8cd8254993835ddb6d047079dbf"/></dir></dir></dir><dir name="Syncer2"><file name="Exception.php" hash="02c7cdc73eaf99357a064cde11a92118"/><dir name="Helper"><file name="Data.php" hash="9a703e543cdd3e6e8b0647e61e86cc61"/></dir><dir name="Model"><file name="Abstract.php" hash="6bf02b02568f9693e26db9fc71c1fd00"/><file name="Exporter.php" hash="8dee6359099c961c238ae01cf4dbc29a"/><file name="Importer.php" hash="8819178eef966f3646c2c10015ffc95f"/></dir><dir name="controllers"><file name="ApiController.php" hash="b344fea43fc81815551e2ec172c6d98f"/></dir><dir name="etc"><file name="adminhtml.xml" hash="b40b1354d89057ce7ab80132b396cbf7"/><file name="config.xml" hash="8abb6452b90d7c7b3d3feef03d0feb48"/><file name="system.xml" hash="6e432cee618a82b158d4b2809cfa1f5f"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Wisepricer_Syncer2.xml" hash="42013aa3e5049f0ad3bd6932a4ad15be"/></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="images"><dir name="wisepricer_syncer2"><file name="wiser_logo.png" hash="324530e55dada5d853c811ab31457db6"/></dir></dir></dir></dir></dir></target><target name="magelib"><dir name="DJJob"><file name="DJJob.php" hash="b692e8d7a53a8a7096ea60d2cfd237f0"/><dir name="examples"><file name="HelloWorldJob.php" hash="3b7a9e4b1f912fb48acf5399f5fe33b9"/></dir><file name="jobs.sql" hash="d73a8213feedadf9dc9eb719fe33b935"/><dir name="test"><file name="custom_table_name.php" hash="3fbebd54f7814c9797107014c9cf0228"/><file name="database.php" hash="82e80af8b072a6276cd46dc4d09f937b"/><file name="original_database_configure.php" hash="e3acccdc25c6f876467c35729505626f"/></dir></dir></target></contents>
|
16 |
+
<compatible/>
|
17 |
+
<dependencies><required><php><min>5.3.0</min><max>6.0.0</max></php></required></dependencies>
|
18 |
+
</package>
|
skin/adminhtml/default/default/images/wisepricer_syncer2/wiser_logo.png
ADDED
Binary file
|