1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46:
47: class Mage_Index_Model_Process extends Mage_Core_Model_Abstract
48: {
49: const XML_PATH_INDEXER_DATA = 'global/index/indexer';
50: 51: 52:
53: const STATUS_RUNNING = 'working';
54: const STATUS_PENDING = 'pending';
55: const STATUS_REQUIRE_REINDEX = 'require_reindex';
56:
57: 58: 59:
60: const EVENT_STATUS_NEW = 'new';
61: const EVENT_STATUS_DONE = 'done';
62: const EVENT_STATUS_ERROR = 'error';
63: const EVENT_STATUS_WORKING = 'working';
64:
65: 66: 67: 68:
69: const MODE_MANUAL = 'manual';
70: const MODE_REAL_TIME = 'real_time';
71:
72: 73: 74: 75: 76:
77: protected $_indexer = null;
78:
79: 80: 81:
82: protected $_isLocked = null;
83: protected $_lockFile = null;
84:
85: 86: 87: 88: 89: 90:
91: protected $_allowTableChanges = true;
92:
93: 94: 95:
96: protected function _construct()
97: {
98: $this->_init('index/process');
99: }
100:
101: 102: 103: 104: 105: 106:
107: protected function _setEventNamespace(Mage_Index_Model_Event $event)
108: {
109: $namespace = get_class($this->getIndexer());
110: $event->setDataNamespace($namespace);
111: $event->setProcess($this);
112: return $this;
113: }
114:
115: 116: 117: 118: 119: 120:
121: protected function _resetEventNamespace($event)
122: {
123: $event->setDataNamespace(null);
124: $event->setProcess(null);
125: return $this;
126: }
127:
128: 129: 130: 131: 132: 133:
134: public function register(Mage_Index_Model_Event $event)
135: {
136: if ($this->matchEvent($event)) {
137: $this->_setEventNamespace($event);
138: $this->getIndexer()->register($event);
139: $event->addProcessId($this->getId());
140: $this->_resetEventNamespace($event);
141: if ($this->getMode() == self::MODE_MANUAL) {
142: $this->_getResource()->updateStatus($this, self::STATUS_REQUIRE_REINDEX);
143: }
144: }
145: return $this;
146:
147: }
148:
149: 150: 151: 152: 153: 154:
155: public function matchEvent(Mage_Index_Model_Event $event)
156: {
157: return $this->getIndexer()->matchEvent($event);
158: }
159:
160: 161: 162: 163: 164: 165: 166:
167: public function matchEntityAndType($entity, $type)
168: {
169: if ($entity !== null && $type !== null) {
170: return $this->getIndexer()->matchEntityAndType($entity, $type);
171: }
172: return true;
173: }
174:
175: 176: 177: 178:
179: public function reindexAll()
180: {
181: if ($this->isLocked()) {
182: Mage::throwException(Mage::helper('index')->__('%s Index process is working now. Please try run this process later.', $this->getIndexer()->getName()));
183: }
184:
185: $processStatus = $this->getStatus();
186:
187: $this->_getResource()->startProcess($this);
188: $this->lock();
189: try {
190: $eventsCollection = $this->getUnprocessedEventsCollection();
191:
192:
193: $eventResource = Mage::getResourceSingleton('index/event');
194:
195: if ($eventsCollection->count() > 0 && $processStatus == self::STATUS_PENDING
196: || $this->getForcePartialReindex()
197: ) {
198: $this->_getResource()->beginTransaction();
199: try {
200: $this->_processEventsCollection($eventsCollection, false);
201: $this->_getResource()->commit();
202: } catch (Exception $e) {
203: $this->_getResource()->rollBack();
204: throw $e;
205: }
206: } else {
207:
208: $eventResource->updateProcessEvents($this);
209: $this->getIndexer()->reindexAll();
210: }
211: $this->unlock();
212:
213: $unprocessedEvents = $eventResource->getUnprocessedEvents($this);
214: if ($this->getMode() == self::MODE_MANUAL && (count($unprocessedEvents) > 0)) {
215: $this->_getResource()->updateStatus($this, self::STATUS_REQUIRE_REINDEX);
216: } else {
217: $this->_getResource()->endProcess($this);
218: }
219: } catch (Exception $e) {
220: $this->unlock();
221: $this->_getResource()->failProcess($this);
222: throw $e;
223: }
224: Mage::dispatchEvent('after_reindex_process_' . $this->getIndexerCode());
225: }
226:
227: 228: 229: 230: 231: 232:
233: public function reindexEverything()
234: {
235: if ($this->getData('runed_reindexall')) {
236: return $this;
237: }
238:
239:
240: $eventResource = Mage::getResourceSingleton('index/event');
241: $unprocessedEvents = $eventResource->getUnprocessedEvents($this);
242: $this->setForcePartialReindex(count($unprocessedEvents) > 0 && $this->getStatus() == self::STATUS_PENDING);
243:
244: if ($this->getDepends()) {
245: $indexer = Mage::getSingleton('index/indexer');
246: foreach ($this->getDepends() as $code) {
247: $process = $indexer->getProcessByCode($code);
248: if ($process) {
249: $process->reindexEverything();
250: }
251: }
252: }
253:
254: $this->setData('runed_reindexall', true);
255: return $this->reindexAll();
256: }
257:
258: 259: 260: 261: 262: 263:
264: public function processEvent(Mage_Index_Model_Event $event)
265: {
266: if (!$this->matchEvent($event)) {
267: return $this;
268: }
269: if ($this->getMode() == self::MODE_MANUAL) {
270: $this->changeStatus(self::STATUS_REQUIRE_REINDEX);
271: return $this;
272: }
273:
274: $this->_getResource()->updateProcessStartDate($this);
275: $this->_setEventNamespace($event);
276: $isError = false;
277:
278: try {
279: $this->getIndexer()->processEvent($event);
280: } catch (Exception $e) {
281: $isError = true;
282: }
283: $event->resetData();
284: $this->_resetEventNamespace($event);
285: $this->_getResource()->updateProcessEndDate($this);
286: $event->addProcessId($this->getId(), $isError ? self::EVENT_STATUS_ERROR : self::EVENT_STATUS_DONE);
287:
288: return $this;
289: }
290:
291: 292: 293: 294: 295:
296: public function getIndexer()
297: {
298: if ($this->_indexer === null) {
299: $code = $this->_getData('indexer_code');
300: if (!$code) {
301: Mage::throwException(Mage::helper('index')->__('Indexer code is not defined.'));
302: }
303: $xmlPath = self::XML_PATH_INDEXER_DATA . '/' . $code;
304: $config = Mage::getConfig()->getNode($xmlPath);
305: if (!$config || empty($config->model)) {
306: Mage::throwException(Mage::helper('index')->__('Indexer model is not defined.'));
307: }
308: $model = Mage::getModel((string)$config->model);
309: if ($model instanceof Mage_Index_Model_Indexer_Abstract) {
310: $this->_indexer = $model;
311: } else {
312: Mage::throwException(Mage::helper('index')->__('Indexer model should extend Mage_Index_Model_Indexer_Abstract.'));
313: }
314: }
315: return $this->_indexer;
316: }
317:
318: 319: 320: 321: 322: 323: 324:
325: public function indexEvents($entity=null, $type=null)
326: {
327: 328: 329:
330: if ($entity !== null && $type !== null) {
331: if (!$this->getIndexer()->matchEntityAndType($entity, $type)) {
332: return $this;
333: }
334: }
335:
336: if ($this->getMode() == self::MODE_MANUAL) {
337: return $this;
338: }
339:
340: if ($this->isLocked()) {
341: return $this;
342: }
343:
344: $this->lock();
345: try {
346: 347: 348:
349: $eventsCollection = $this->getUnprocessedEventsCollection();
350: if ($entity !== null) {
351: $eventsCollection->addEntityFilter($entity);
352: }
353: if ($type !== null) {
354: $eventsCollection->addTypeFilter($type);
355: }
356:
357: $this->_processEventsCollection($eventsCollection);
358: $this->unlock();
359: } catch (Exception $e) {
360: $this->unlock();
361: throw $e;
362: }
363: return $this;
364: }
365:
366: 367: 368: 369: 370: 371: 372:
373: protected function _processEventsCollection(
374: Mage_Index_Model_Resource_Event_Collection $eventsCollection,
375: $skipUnmatched = true
376: ) {
377:
378:
379: while ($event = $eventsCollection->fetchItem()) {
380: try {
381: $this->processEvent($event);
382: if (!$skipUnmatched) {
383: $eventProcessIds = $event->getProcessIds();
384: if (!isset($eventProcessIds[$this->getId()])) {
385: $event->addProcessId($this->getId(), null);
386: }
387: }
388: } catch (Exception $e) {
389: $event->addProcessId($this->getId(), self::EVENT_STATUS_ERROR);
390: }
391: $event->save();
392: }
393: return $this;
394: }
395:
396: 397: 398: 399: 400: 401: 402:
403: public function updateEventStatus(Mage_Index_Model_Event $event, $status)
404: {
405: $this->_getResource()->updateEventStatus($this->getId(), $event->getId(), $status);
406: return $this;
407: }
408:
409: 410: 411: 412: 413:
414: protected function _getLockFile()
415: {
416: if ($this->_lockFile === null) {
417: $varDir = Mage::getConfig()->getVarDir('locks');
418: $file = $varDir . DS . 'index_process_'.$this->getId().'.lock';
419: if (is_file($file)) {
420: $this->_lockFile = fopen($file, 'w');
421: } else {
422: $this->_lockFile = fopen($file, 'x');
423: }
424: fwrite($this->_lockFile, date('r'));
425: }
426: return $this->_lockFile;
427: }
428:
429: 430: 431: 432: 433: 434:
435: public function lock()
436: {
437: $this->_isLocked = true;
438: flock($this->_getLockFile(), LOCK_EX | LOCK_NB);
439: return $this;
440: }
441:
442: 443: 444: 445: 446: 447: 448:
449: public function lockAndBlock()
450: {
451: $this->_isLocked = true;
452: flock($this->_getLockFile(), LOCK_EX);
453: return $this;
454: }
455:
456: 457: 458: 459: 460:
461: public function unlock()
462: {
463: $this->_isLocked = false;
464: flock($this->_getLockFile(), LOCK_UN);
465: return $this;
466: }
467:
468: 469: 470: 471: 472:
473: public function isLocked()
474: {
475: if ($this->_isLocked !== null) {
476: return $this->_isLocked;
477: } else {
478: $fp = $this->_getLockFile();
479: if (flock($fp, LOCK_EX | LOCK_NB)) {
480: flock($fp, LOCK_UN);
481: return false;
482: }
483: return true;
484: }
485: }
486:
487: 488: 489:
490: public function __destruct()
491: {
492: if ($this->_lockFile) {
493: fclose($this->_lockFile);
494: }
495: }
496:
497: 498: 499: 500: 501: 502:
503: public function changeStatus($status)
504: {
505: Mage::dispatchEvent('index_process_change_status', array(
506: 'process' => $this,
507: 'status' => $status
508: ));
509: $this->_getResource()->updateStatus($this, $status);
510: return $this;
511: }
512:
513: 514: 515: 516: 517:
518: public function getModesOptions()
519: {
520: return array(
521: self::MODE_REAL_TIME => Mage::helper('index')->__('Update on Save'),
522: self::MODE_MANUAL => Mage::helper('index')->__('Manual Update')
523: );
524: }
525:
526: 527: 528: 529: 530:
531: public function getStatusesOptions()
532: {
533: return array(
534: self::STATUS_PENDING => Mage::helper('index')->__('Ready'),
535: self::STATUS_RUNNING => Mage::helper('index')->__('Processing'),
536: self::STATUS_REQUIRE_REINDEX => Mage::helper('index')->__('Reindex Required'),
537: );
538: }
539:
540: 541: 542: 543: 544:
545: public function getUpdateRequiredOptions()
546: {
547: return array(
548: 0 => Mage::helper('index')->__('No'),
549: 1 => Mage::helper('index')->__('Yes'),
550: );
551: }
552:
553: 554: 555: 556: 557:
558: public function getDepends()
559: {
560: $depends = $this->getData('depends');
561: if (is_null($depends)) {
562: $depends = array();
563: $path = self::XML_PATH_INDEXER_DATA . '/' . $this->getIndexerCode();
564: $node = Mage::getConfig()->getNode($path);
565: if ($node) {
566: $data = $node->asArray();
567: if (isset($data['depends']) && is_array($data['depends'])) {
568: $depends = array_keys($data['depends']);
569: }
570: }
571:
572: $this->setData('depends', $depends);
573: }
574:
575: return $depends;
576: }
577:
578: 579: 580: 581: 582: 583: 584:
585: public function setAllowTableChanges($value = true)
586: {
587: $this->_allowTableChanges = $value;
588: return $this;
589: }
590:
591: 592: 593: 594: 595:
596: public function disableIndexerKeys()
597: {
598: $indexer = $this->getIndexer();
599: if ($indexer) {
600: $indexer->disableKeys();
601: }
602: return $this;
603: }
604:
605: 606: 607: 608: 609:
610: public function enableIndexerKeys()
611: {
612: $indexer = $this->getIndexer();
613: if ($indexer) {
614: $indexer->enableKeys();
615: }
616: return $this;
617: }
618:
619: 620: 621: 622: 623: 624:
625: public function safeProcessEvent(Mage_Index_Model_Event $event)
626: {
627: if ($this->isLocked()) {
628: return $this;
629: }
630: if (!$this->matchEvent($event)) {
631: return $this;
632: }
633: $this->lock();
634: try {
635: $this->processEvent($event);
636: $this->unlock();
637: } catch (Exception $e) {
638: $this->unlock();
639: throw $e;
640: }
641: return $this;
642: }
643:
644: 645: 646: 647: 648:
649: public function getUnprocessedEventsCollection()
650: {
651:
652: $eventsCollection = Mage::getResourceModel('index/event_collection');
653: $eventsCollection->addProcessFilter($this, self::EVENT_STATUS_NEW);
654: return $eventsCollection;
655: }
656: }
657: