diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 067e1a2efdb5535af30ba3dfb9aa7ec7bb0015ad..b6429001e935f4dde416dccd0889618affd9f208 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ Feeds 6.x 1.0 XXXXXXXXXXXXXXXXXX -------------------------------- +- #849986 lyricnz, alex_b: Cleaner batch support. - #866492 lyricnz: Clean up tests. - #862444 pounard: Do not name files after their enclosure class. - #851570 morningtime: Avoid trailing slashes when passing file paths to diff --git a/includes/FeedsBatch.inc b/includes/FeedsBatch.inc index 6d871aef19c0b400eea76b8651f5845e9c08460c..fe95c0966e221c7a3f804fbe80cf860880c17346 100644 --- a/includes/FeedsBatch.inc +++ b/includes/FeedsBatch.inc @@ -1,26 +1,85 @@ <?php // $Id$ +// Batch stages. +define('FEEDS_FETCHING', 'fetching'); +define('FEEDS_PARSING', 'parsing'); +define('FEEDS_PROCESSING', 'processing'); +define('FEEDS_CLEARING', 'clearing'); + /** * A FeedsBatch object holds the state of an import or clear batch. * * Used in FeedsSource class. Counter variables are public for easier access. */ class FeedsBatch { - // Maximum number of items in this batch. This is not necessarily the current - // number of items in this batch - public $total; - // Number of items created. - public $created; - // Number of items updated or replaced. - public $updated; - // Number of items deleted. - public $deleted; + // Total of each stage of this batch. + protected $total; + // Progress of each stage of this batch. + protected $progress; public function __construct() { - $this->total = 0; - $this->created = 0; - $this->updated = 0; - $this->deleted = 0; + $this->total = array(); + $this->progress = array(); + } + + /** + * Set the total for a stage. + */ + public function setTotal($stage, $total) { + $this->total[$stage] = $total; + } + + /** + * Get the total for a stage. + */ + public function getTotal($stage) { + return $this->total[$stage]; + } + + /** + * Set progress for a stage. + * + * @param $stage + * The stage to set the progress for. One of FEEDS_FETCHING, FEEDS_PARSING, + * FEEDS_PROCESING or FEEDS_CLEARING. + * @param $progress + * The number of items worked off for the given stage. This should be the + * number of items worked off across all page loads, not just the present + * page load. + */ + public function setProgress($stage, $progress) { + $this->progress[$stage] = $progress; + } + + /** + * Report progress. + * + * @param $stage + * The stage to set the progress for. One of FEEDS_FETCHING, FEEDS_PARSING, + * FEEDS_PROCESING or FEEDS_CLEARING. + */ + public function getProgress($stage = NULL) { + if ($stage) { + $progress = $this->progress[$stage]; + if ($progress == FEEDS_BATCH_COMPLETE) { + return FEEDS_BATCH_COMPLETE; + } + $total = $this->total[$stage]; + } + else { + $complete = TRUE; + $progress = 0; + foreach ($this->progress as $p) { + $progress += $p; + $complete &= $p == FEEDS_BATCH_COMPLETE; + } + if ($complete) { + return FEEDS_BATCH_COMPLETE; + } + $total = array_sum($this->total); + } + $progress = (1.0 / $total) * $progress; + return $progress == FEEDS_BATCH_COMPLETE ? 0.999 : $progress; } } @@ -52,10 +111,33 @@ class FeedsBatch { * } * @endcode * + * If a processing task is very slow, it can be batched over multiple page + * loads. For batching the consumer loop can be left while the current progress + * is set on the batch object. If the current progress is not + * FEEDS_BATCH_COMPLETE the processor will be called again on a subsequent page + * load to continue where it has left off. For an example, see + * FeedsNodeProcessor::process(). + * + * @code + * $created = 0; + * while ($item = $batch->shiftItem()) { + * $object = $this->map($item); + * $object->save(); + * $created++; // Created in this page load. + * $batch->created++; // Created total. + * if ($created > MAX_CREATED) { + * $batch->setProgress(FEEDS_PROCESSING, $batch->created); + * return; + * } + * } + * $batch->setProgress(FEEDS_PROCESSING, FEEDS_BATCH_COMPLETE); + * @endcode + * * Note: Knowledge of the internal structure of a single item in the $items * array is managed by the mapping API specified in FeedsParser class and * FeedsProcessor class. * + * @see FeedsBatch * @see FeedsFileBatch * @see FeedsHTTPBatch */ @@ -65,13 +147,23 @@ class FeedsImportBatch extends FeedsBatch { protected $link; protected $items; protected $raw; + public $created; + public $updated; public function __construct($raw = '') { - $this->raw = $raw; + parent::__construct(); + $this->progress = array( + FEEDS_FETCHING => FEEDS_BATCH_COMPLETE, + FEEDS_PARSING => FEEDS_BATCH_COMPLETE, + FEEDS_PROCESSING => FEEDS_BATCH_COMPLETE, + ); $this->title = ''; $this->description = ''; $this->link = ''; $this->items = array(); + $this->raw = $raw; + $this->created = 0; + $this->updated = 0; } /** @@ -170,7 +262,6 @@ class FeedsImportBatch extends FeedsBatch { */ public function setItems($items) { $this->items = $items; - $this->total = count($this->items); } /** @@ -178,6 +269,27 @@ class FeedsImportBatch extends FeedsBatch { */ public function addItem($item) { $this->items[] = $item; - $this->total = count($this->items); + } + + /** + * Get number of items. + */ + public function getItemCount() { + return count($this->items); + } +} + +/** + * Batch class for batched deleting of items. + */ +class FeedsClearBatch extends FeedsBatch { + // Number of items deleted. + public $deleted; + public function __construct() { + parent::__construct(); + $this->progress = array( + FEEDS_CLEARING => FEEDS_BATCH_COMPLETE, + ); + $this->deleted = 0; } } diff --git a/includes/FeedsSource.inc b/includes/FeedsSource.inc index f06c89d4a81d4c56e5cecbafeaae557b2b572372..3d942a25b6d75f8a70108e4efd6d53753ca10925 100644 --- a/includes/FeedsSource.inc +++ b/includes/FeedsSource.inc @@ -141,7 +141,8 @@ class FeedsSource extends FeedsConfigurable { $this->batch = $this->importer->fetcher->fetch($this); $this->importer->parser->parse($this->batch, $this); } - $result = $this->importer->processor->process($this->batch, $this); + $this->importer->processor->process($this->batch, $this); + $result = $this->batch->getProgress(); if ($result == FEEDS_BATCH_COMPLETE) { unset($this->batch); module_invoke_all('feeds_after_import', $this->importer, $this); @@ -170,10 +171,11 @@ class FeedsSource extends FeedsConfigurable { try { $this->importer->fetcher->clear($this); $this->importer->parser->clear($this); - if (!$this->batch) { - $this->batch = new FeedsBatch(); + if (!$this->batch || !($this->batch instanceof FeedsClearBatch)) { + $this->batch = new FeedsClearBatch(); } - $result = $this->importer->processor->clear($this->batch, $this); + $this->importer->processor->clear($this->batch, $this); + $result = $this->batch->getProgress(); if ($result == FEEDS_BATCH_COMPLETE) { unset($this->batch); } diff --git a/plugins/FeedsDataProcessor.inc b/plugins/FeedsDataProcessor.inc index bc83215a4709c7f7d8c695ecae557ea89e2c3c55..353d5da725a3082450ccbd51f613d30fc4d04ca2 100644 --- a/plugins/FeedsDataProcessor.inc +++ b/plugins/FeedsDataProcessor.inc @@ -60,8 +60,6 @@ class FeedsDataProcessor extends FeedsProcessor { else { drupal_set_message(t('There are no new items.')); } - - return FEEDS_BATCH_COMPLETE; } /** @@ -75,7 +73,6 @@ class FeedsDataProcessor extends FeedsProcessor { ); $num = $this->handler()->delete($clause); drupal_set_message(format_plural($num, 'Deleted @number item.', 'Deleted @number items.', array('@number' => $num))); - return FEEDS_BATCH_COMPLETE; } /** diff --git a/plugins/FeedsFeedNodeProcessor.inc b/plugins/FeedsFeedNodeProcessor.inc index fb5b859a3432d917783bcea670dad0f6181197e1..208e6965a3b9316268fe7f275befc439b6dca7ef 100644 --- a/plugins/FeedsFeedNodeProcessor.inc +++ b/plugins/FeedsFeedNodeProcessor.inc @@ -53,8 +53,6 @@ class FeedsFeedNodeProcessor extends FeedsProcessor { else { drupal_set_message(t('There is no new content.')); } - - return FEEDS_BATCH_COMPLETE; } /** diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc index adaf74cb8c34cc17e5646ea9d2c795b06a156af5..567c3554b93965a08ae26f02a05334d88422e57e 100644 --- a/plugins/FeedsNodeProcessor.inc +++ b/plugins/FeedsNodeProcessor.inc @@ -25,8 +25,11 @@ class FeedsNodeProcessor extends FeedsProcessor { */ public function process(FeedsImportBatch $batch, FeedsSource $source) { - // Keep track of processed items in this pass. + // Keep track of processed items in this pass, set total number of items. $processed = 0; + if (!$batch->getTotal(FEEDS_PROCESSING)) { + $batch->setTotal(FEEDS_PROCESSING, $batch->getItemCount()); + } while ($item = $batch->shiftItem()) { @@ -60,7 +63,8 @@ class FeedsNodeProcessor extends FeedsProcessor { $processed++; if ($processed >= variable_get('feeds_node_batch_size', FEEDS_NODE_BATCH_SIZE)) { - return (1.0 / ($batch->total + 1)) * ($batch->updated + $batch->created); // Add + 1 to make sure that result is not 1.0 = finished. + $batch->setProgress(FEEDS_PROCESSING, $batch->created + $batch->updated); + return; } } @@ -74,16 +78,16 @@ class FeedsNodeProcessor extends FeedsProcessor { else { drupal_set_message(t('There is no new content.')); } - - return FEEDS_BATCH_COMPLETE; + $batch->setProgress(FEEDS_PROCESSING, FEEDS_BATCH_COMPLETE); } /** * Implementation of FeedsProcessor::clear(). */ public function clear(FeedsBatch $batch, FeedsSource $source) { - if (empty($batch->total)) { - $batch->total = db_result(db_query("SELECT COUNT(nid) FROM {feeds_node_item} WHERE id = '%s' AND feed_nid = %d", $source->id, $source->feed_nid)); + if (!$batch->getTotal(FEEDS_CLEARING)) { + $total = db_result(db_query("SELECT COUNT(nid) FROM {feeds_node_item} WHERE id = '%s' AND feed_nid = %d", $source->id, $source->feed_nid)); + $batch->setTotal(FEEDS_CLEARING, $total); } $result = db_query_range("SELECT nid FROM {feeds_node_item} WHERE id = '%s' AND feed_nid = %d", $source->id, $source->feed_nid, 0, variable_get('feeds_node_batch_size', FEEDS_NODE_BATCH_SIZE)); while ($node = db_fetch_object($result)) { @@ -91,7 +95,8 @@ class FeedsNodeProcessor extends FeedsProcessor { $batch->deleted++; } if (db_result(db_query_range("SELECT nid FROM {feeds_node_item} WHERE id = '%s' AND feed_nid = %d", $source->id, $source->feed_nid, 0, 1))) { - return (1.0 / ($batch->total + 1)) * $batch->deleted; + $batch->setProgress(FEEDS_CLEARING, $batch->deleted); + return; } // Set message. @@ -102,7 +107,7 @@ class FeedsNodeProcessor extends FeedsProcessor { else { drupal_set_message(t('There is no content to be deleted.')); } - return FEEDS_BATCH_COMPLETE; + $batch->setProgress(FEEDS_CLEARING, FEEDS_BATCH_COMPLETE); } /** diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index 4f049d7707cb7566821cba906ea7980b119ed653..54a79e203a40805081a2a432628287f5a0c22927 100644 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -20,10 +20,6 @@ abstract class FeedsProcessor extends FeedsPlugin { * The current feed import data passed in from the parsing stage. * @param FeedsSource $source * Source information about this import. - * - * @return - * FEEDS_BATCH_COMPLETE if all items have been processed, a float between 0 - * and 0.99* indicating progress otherwise. */ public abstract function process(FeedsImportBatch $batch, FeedsSource $source); @@ -40,10 +36,6 @@ abstract class FeedsProcessor extends FeedsPlugin { * item pertains to a certain souce is by using $source->feed_nid. It is the * processor's responsibility to store the feed_nid of an imported item in * the processing stage. - * - * @return - * FEEDS_BATCH_COMPLETE if all items have been processed, a float between 0 - * and 0.99* indicating progress otherwise. */ public abstract function clear(FeedsBatch $batch, FeedsSource $source); diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc index fa714d7eb4dc2657f416e76f99c63fce4835e46d..a3030d6d05873572a4fe2d5c804459a97fba0076 100644 --- a/plugins/FeedsTermProcessor.inc +++ b/plugins/FeedsTermProcessor.inc @@ -82,8 +82,6 @@ class FeedsTermProcessor extends FeedsProcessor { else { drupal_set_message(t('There are no new terms.')); } - - return FEEDS_BATCH_COMPLETE; } /** @@ -108,7 +106,6 @@ class FeedsTermProcessor extends FeedsProcessor { else { drupal_set_message(t('No terms to be deleted.')); } - return FEEDS_BATCH_COMPLETE; } /** diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc index c0b0605daee0a8a353c177ae13429387c6600e36..09722128f5fc223b7d3edd5f4f2236f23bfce00a 100644 --- a/plugins/FeedsUserProcessor.inc +++ b/plugins/FeedsUserProcessor.inc @@ -80,8 +80,6 @@ class FeedsUserProcessor extends FeedsProcessor { else { drupal_set_message(t('There are no new users.')); } - - return FEEDS_BATCH_COMPLETE; } /**