From: <gem...@li...> - 2012-01-19 17:17:51
|
Revision: 412 http://gemstracker.svn.sourceforge.net/gemstracker/?rev=412&view=rev Author: matijsdejong Date: 2012-01-19 17:17:39 +0000 (Thu, 19 Jan 2012) Log Message: ----------- Batch processing works (#35) and is implemented for SurveyMaintenanceAction.php checks. Modified Paths: -------------- trunk/library/classes/Gems/Default/SourceAction.php trunk/library/classes/Gems/Default/SurveyMaintenanceAction.php trunk/library/classes/Gems/Tracker/Batch/ProcessTokensBatch.php trunk/library/classes/Gems/Tracker/TrackerInterface.php trunk/library/classes/Gems/Tracker.php trunk/library/classes/MUtil/Batch/BatchAbstract.php trunk/library/classes/MUtil/Batch/BatchPull.js trunk/library/classes/MUtil/Batch/BatchPush.js trunk/library/classes/MUtil/Batch/WaitBatch.php trunk/library/classes/MUtil/ProgressBar/Adapter/JsPush.php Modified: trunk/library/classes/Gems/Default/SourceAction.php =================================================================== --- trunk/library/classes/Gems/Default/SourceAction.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/Gems/Default/SourceAction.php 2012-01-19 17:17:39 UTC (rev 412) @@ -246,77 +246,34 @@ /* $batch = new MUtil_Batch_WaitBatch(); - $batch->method = MUtil_Batch_BatchAbstract::PUSH; - if ($batch->isLoaded()) { - if ($batch->isFinished()) { - if (! $batch->showReport($this->getRequest())) { - $batch->reset(); - } - } else { - if ($batch->hasStarted($this->getRequest())) { - $batch->runAll(); - exit; - } - } - } else { - $batch->addWaits(4, 2); - $batch->addWaitsLater(15, 1); - $batch->addWaits(1, 1); - $batch->addWaitsLater(1, 2); - $batch->addWaits(4); - } - if ($batch->showReport($this->getRequest())) { - $this->addMessage($batch->getReport()); - $batch->reset(); - $this->html->pInfo()->actionLink(array($batch->progressParameterName => null), $this->_('Restart')); - } else { - $this->html->append($batch->getPanel($this->view)); - } - // */ - /* - // $batch = new Gems_Tracker_Batch_SynchronizeSourceBatch(); - // $batch->method = 'Push'; - if (! $batch->isLoaded()) { - foreach ($data as $row) { - $batch->addSource($row['gso_id_source'], $row['gso_source_name']); - } - } + // $batch->setMethodPush(5); + // $batch->autoStart = true; + // $batch->minimalStepDurationMs = 2000; if ($batch->run($this->getRequest())) { - if ($batch->isReady()) { - $this->addMessage($batch->getMessages()); - $this->afterSaveRoute($this->getRequest()); - } + exit; } else { - $this->html[] = $batch->getPanel(); - } - // */ - /* - $progress = $this->html->progress('0%'); - $progress->method = 'Push'; - // $progress = $this->html->progress(); - if ($progress->run($this->getRequest())) { - - // IIS 7: %windir%\System32\inetsrv\config\applicationHost.config - // ../handlers/add name="PHP_via_FastCGI" - // ../handlers/add name="CGI-exe" - // add attribute responseBufferLimit="1024" - - for ($i = 50; $i < 100; $i += 1) { - if ($i < 20) { - $text = 'Just beginning'; - } else if ($i < 50) { - $text = 'A bit done'; - } else if ($i < 80) { - $text = 'Getting closer'; + if ($batch->isFinished()) { + $this->addMessage($batch->getMessages(true)); + $this->html->pInfo($batch->getRestartButton($this->_('Prepare restart'), array('class' => 'actionlink'))); + } else { + // Populate the batch (from scratch). + $batch->reset(); + if (true) { + $batch->addWaitsMs(400, 20); + $batch->addWaits(2, 1, 'Har har'); + $batch->addWaitsMs(20, 50); } else { - $text = 'Nearly done'; + $batch->addWaits(4, 2); + $batch->addWaits(2, 1); + $batch->addWaitsLater(15, 1); + $batch->addWait(4, 'That took some time!'); + $batch->addWait(4, 'So we see the message. :)'); + $batch->addWaitsLater(1, 2); + $batch->addWaits(4); } - // IIS? - // echo str_repeat(' ',1024*3); - $progress->update($i, ' ' . $text); - sleep(1); + $this->html->pInfo($batch->getStartButton($this->_('Start synchronization'))); + $this->html->append($batch->getPanel($this->view, '0%')); } - $progress->finish(); } // */ if ($data) { Modified: trunk/library/classes/Gems/Default/SurveyMaintenanceAction.php =================================================================== --- trunk/library/classes/Gems/Default/SurveyMaintenanceAction.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/Gems/Default/SurveyMaintenanceAction.php 2012-01-19 17:17:39 UTC (rev 412) @@ -268,42 +268,52 @@ $surveyId = $this->_getParam(MUtil_Model::REQUEST_ID); $where = $this->db->quoteInto('gto_id_survey = ?', $surveyId); - /* $batch = $this->loader->getTracker()->recalculateTokensBatch($this->loader->getCurrentUser()->getUserId(), $where); - if ($batch->hasStarted($this->getRequest())) { - // TODO + if ($batch->run($this->getRequest())) { + exit; } else { $this->html->h3( sprintf($this->_('Checking survey results for the %s survey.'), $this->db->fetchOne("SELECT gsu_survey_name FROM gems__surveys WHERE gsu_id_survey = ?", $surveyId))); - if ($batch->isLoaded()) { - $this->html->pInfo(sprintf($this->_('Running check for %s tokens...'), $batch->count())); - $this->html->append($batch->getPanel()); + if ($batch->isFinished()) { + $this->addMessage($batch->getMessages(true)); + $this->html->pInfo($batch->getRestartButton($this->_('Prepare recheck'), array('class' => 'actionlink'))); } else { - $this->html->pInfo($this->_('No tokens to check.')); + if ($batch->count()) { + // Batch is loaded by Tracker + $this->html->pInfo($batch->getStartButton(sprintf($this->_('Check %s tokens'), $batch->count()))); + $this->html->append($batch->getPanel($this->view, '0%')); + } else { + $this->html->pInfo($this->_('No tokens to check.')); + } } } - // */ - - //* - $this->addMessage(sprintf($this->_( - 'Checking survey results for the %s survey.'), - $this->db->fetchOne("SELECT gsu_survey_name FROM gems__surveys WHERE gsu_id_survey = ?", $surveyId))); - - $this->addMessage($this->loader->getTracker()->recalculateTokens($this->session->user_id, $where)); - - $this->afterSaveRoute($this->getRequest()); - - // */ } public function checkAllAction() { - $this->addMessage($this->loader->getTracker()->recalculateTokens($this->session->user_id)); + $batch = $this->loader->getTracker()->recalculateTokensBatch($this->loader->getCurrentUser()->getUserId()); - $this->afterSaveRoute($this->getRequest()); + if ($batch->run($this->getRequest())) { + exit; + } else { + $this->html->h3($this->_('Checking survey results for all surveys.')); + + if ($batch->isFinished()) { + $this->addMessage($batch->getMessages(true)); + $this->html->pInfo($batch->getRestartButton($this->_('Prepare recheck'), array('class' => 'actionlink'))); + } else { + if ($batch->count()) { + // Batch is loaded by Tracker + $this->html->pInfo($batch->getStartButton(sprintf($this->_('Check %s tokens'), $batch->count()))); + $this->html->append($batch->getPanel($this->view, '0%')); + } else { + $this->html->pInfo($this->_('No tokens to check.')); + } + } + } } /** Modified: trunk/library/classes/Gems/Tracker/Batch/ProcessTokensBatch.php =================================================================== --- trunk/library/classes/Gems/Tracker/Batch/ProcessTokensBatch.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/Gems/Tracker/Batch/ProcessTokensBatch.php 2012-01-19 17:17:39 UTC (rev 412) @@ -50,15 +50,14 @@ * * @var Gems_Tracker */ - public $tracker; + protected $tracker; - public function __construct($where, Gems_Tracker $tracker) - { - parent::__construct(__CLASS__ . '::' . $where); + /** + * + * @var Zend_Translate + */ + protected $translate; - $this->tracker = $tracker; - } - public function addToken($tokenData, $userId) { if (is_array($tokenData)) { @@ -71,7 +70,7 @@ } // MUtil_Echo::track($tokenData); - $this->setStep('checkTokenCompletion', 'tokchk-' . $tokenId, $tokenData); + $this->setStep('checkTokenCompletion', 'tokchk-' . $tokenId, $tokenData, $userId); return $this; } @@ -105,6 +104,68 @@ } } + public function getCounterMessages() + { + if ($this->getCounter('checkedRespondentTracks')) { + $messages[] = sprintf($this->translate->_('Checked %d tracks.'), $this->getCounter('checkedRespondentTracks')); + } + if ($this->getCounter('checkedTokens') || (! $this->getCounter('checkedRespondentTracks'))) { + $messages[] = sprintf($this->translate->_('Checked %d tokens.'), $this->getCounter('checkedTokens')); + } + + if ($this->hasChanged()) { + if ($this->getCounter('surveyCompletionChanges')) { + $messages[] = sprintf($this->translate->_('Answers changed by survey completion event for %d tokens.'), $this->getCounter('surveyCompletionChanges')); + } + if ($this->getCounter('resultDataChanges')) { + $messages[] = sprintf($this->translate->_('Results and timing changed for %d tokens.'), $this->getCounter('resultDataChanges')); + } + if ($this->getCounter('roundCompletionChanges')) { + $messages[] = sprintf($this->translate->_('%d token round completion events caused changed to %d tokens.'), $this->getCounter('roundCompletionCauses'), $this->getCounter('roundCompletionChanges')); + } + if ($this->getCounter('tokenDateChanges')) { + $messages[] = sprintf($this->translate->_('%2$d token date changes in %1$d tracks.'), $this->getCounter('tokenDateCauses'), $this->getCounter('tokenDateChanges')); + } + if ($this->getCounter('roundChangeUpdates')) { + $messages[] = sprintf($this->translate->_('Round changes propagated to %d tokens.'), $this->getCounter('roundChangeUpdates')); + } + if ($this->getCounter('deletedTokens')) { + $messages[] = sprintf($this->translate->_('%d tokens deleted by round changes.'), $this->getCounter('deletedTokens')); + } + if ($this->getCounter('createdTokens')) { + $messages[] = sprintf($this->translate->_('%d tokens created to by round changes.'), $this->getCounter('createdTokens')); + } + } else { + $messages[] = $this->translate->_('No tokens were changed.'); + } + + return $messages; + } + + /** + * String of messages from the batch + * + * Do not forget to reset() the batch if you're done with it after + * displaying the report. + * + * @param boolean $reset When true the batch is reset afterwards + * @return array + */ + public function getMessages($reset = false) + { + return array_merge($this->getCounterMessages(), parent::getMessages($reset)); + } + + public function hasChanged() + { + return $this->getCounter('resultDataChanges') || + $this->getCounter('surveyCompletionChanges') || + $this->getCounter('roundCompletionChanges') || + $this->getCounter('tokenDateCauses') || + $this->getCounter('roundChangeUpdates') || + $this->getCounter('createdTokens'); + } + protected function processTokenCompletion($tokenData, $userId) { $token = $this->tracker->getToken($tokenData); @@ -118,7 +179,7 @@ } $trackId = $respTrack->getRespondentTrackId(); - $this->setStep('checkTrackTokens', 'chktrck-' . $trackId, $trackId, $userid); + $this->setStep('checkTrackTokens', 'chktrck-' . $trackId, $trackId, $userId); } } } Modified: trunk/library/classes/Gems/Tracker/TrackerInterface.php =================================================================== --- trunk/library/classes/Gems/Tracker/TrackerInterface.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/Gems/Tracker/TrackerInterface.php 2012-01-19 17:17:39 UTC (rev 412) @@ -288,4 +288,17 @@ * @return array of translated messages */ public function recalculateTokens($userId = null, $cond = null); + + /** + * Recalculates all token dates, timing and results + * and outputs text messages. + * + * Does not reflect changes to tracks or rounds. + * + * @param Zend_Translate $t + * @param int $userId Id of the user who takes the action (for logging) + * @param string $cond + * @return Gems_Tracker_Batch_ProcessTokensBatch A batch to process the changes + */ + public function recalculateTokensBatch($userId = null, $cond = null); } Modified: trunk/library/classes/Gems/Tracker.php =================================================================== --- trunk/library/classes/Gems/Tracker.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/Gems/Tracker.php 2012-01-19 17:17:39 UTC (rev 412) @@ -838,7 +838,7 @@ { $where = implode(' ', $tokenSelect->getSelect()->getPart(Zend_Db_Select::WHERE)); - $batch = new Gems_Tracker_Batch_ProcessTokensBatch($where, $this); + $batch = $this->_loadClass('Batch_ProcessTokensBatch', true); //, array($where, $this)); if (! $batch->isLoaded()) { $tokenRows = $tokenSelect->fetchAll(); Modified: trunk/library/classes/MUtil/Batch/BatchAbstract.php =================================================================== --- trunk/library/classes/MUtil/Batch/BatchAbstract.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/MUtil/Batch/BatchAbstract.php 2012-01-19 17:17:39 UTC (rev 412) @@ -46,11 +46,15 @@ * Each step in the sequence consists of a method name of the child object * and any number of scalar variables and array's containing scalar variables. * + * See MUtil_Batch_WaitBatch for example usage. + * * A nice future extension would be to separate the storage engine used so we * could use e.g. Zend_Queue as an alternative for storing the command stack. * However, as this package needs more state info than available in Zend_Queue * we would need an extra extension for that. * + * @see MUtil_Batch_WaitBatch + * * @package MUtil * @subpackage Batch * @copyright Copyright (c) 2012 Erasmus MC @@ -94,17 +98,40 @@ * * @var boolean */ - public $autoStart = true; + public $autoStart = false; /** - * The mode to use for the panel: push or pull + * The number of bytes to pad during push communication in Kilobytes. * + * This is needed as many servers need extra output passing to avoid buffering. + * + * Also this allows you to keep the server buffer high while using this JsPush. + * + * @var int + */ + public $extraPushPaddingKb = 0; + + /** + * The mode to use for the panel: PUSH or PULL + * * @var string */ - public $method = self::PULL; + protected $method = self::PULL; /** + * The minimal time used between send progress reports. * + * This enables quicker processing as multiple steps can be taken in a single + * run(), without the run taking too long to answer. + * + * Set to 0 to report back on each step. + * + * @var int + */ + public $minimalStepDurationMs = 1000; + + /** + * * @var Zend_ProgressBar */ protected $progressBar; @@ -230,66 +257,108 @@ */ protected function addToCounter($name, $add = 1) { - if (! isset($this->session->counters[$name])) { - $this->session->counters[$name] = 0; + if (! isset($this->_session->counters[$name])) { + $this->_session->counters[$name] = 0; } - $this->session->counters[$name] += $add; + $this->_session->counters[$name] += $add; - return $this->session->counters[$name]; + return $this->_session->counters[$name]; } - /** - * Count the number of commands + /** + * The number of commands in this batch (both processed + * and unprocessed). * - * @return int The custom count as an integer. - */ - public function count() + * @return int + */ + public function count() { - return count($this->_session->commands); + return count($this->_session->commands) + $this->_session->processed; } /** - * Returns the prefix used for the function names to avoid naming clashes. + * Return the value of a named counter * + * @param string $name + * @return integer + */ + public function getCounter($name) + { + MUtil_Echo::track($this->_session->counters); + if (isset($this->_session->counters[$name])) { + return $this->_session->counters[$name]; + } + + return 0; + } + + /** + * Returns the prefix used for the function names for the PUSH method to avoid naming clashes. + * + * Set automatically to get_class($this) . '_' $this->_id . '_', use different name + * in case of name clashes. + * + * @see setFunctionPrefix() + * * @return string */ - public function getFunctionPrefix() + protected function getFunctionPrefix() { if (! $this->_functionPrefix) { - $this->setFunctionPrefix(__CLASS__ . '_' . $this->_id . '_'); + $this->setFunctionPrefix(get_class($this) . '_' . $this->_id . '_'); } return (string) $this->_functionPrefix; } /** - * Return + * String of messages from the batch + * + * Do not forget to reset() the batch if you're done with it after + * displaying the report. + * + * @param boolean $reset When true the batch is reset afterwards + * @return array + */ + public function getMessages($reset = false) + { + $messages = $this->_session->messages; + + if ($reset) { + $this->reset(); + } + + return $messages; + } + + /** + * Return a progress panel object, set up to be used by + * this batch. + * + * @param Zend_View_Abstract $view + * @param mixed $arg_array MUtil_Ra::args() arguments to populate progress bar with * @return MUtil_Html_ProgressPanel */ - public function getPanel(Zend_View_Abstract $view) + public function getPanel(Zend_View_Abstract $view, $arg_array = null) { + $args = func_get_args(); + ZendX_JQuery::enableView($view); - if ($this->isFinished()) { - $content = '100%'; - } else { - $content = '100%'; - } + $urlFinish = $view->url(array($this->progressParameterName => $this->progressParameterReportValue)); + $urlRun = $view->url(array($this->progressParameterName => $this->progressParameterRunValue)); - $panel = new MUtil_Html_ProgressPanel('0%'); - + $panel = new MUtil_Html_ProgressPanel($args); $panel->id = $this->_id; - $panel->method = $this->method; $js = new MUtil_Html_Code_JavaScript(dirname(__FILE__) . '/Batch' . $this->method . '.js'); $js->setInHeader(false); // Set the fields, in case they where not set earlier $js->setDefault('__AUTOSTART__', $this->autoStart ? 'true' : 'false'); - $js->setDefault('{ID}', $this->_id); - $js->setDefault('{TEXT_TAG}', $panel->getDefaultChildTag()); - $js->setDefault('{TEXT_CLASS}', $panel->progressTextClass); - $js->setDefault('{URL_FINISH}', addcslashes($view->url(array($this->progressParameterName => $this->progressParameterReportValue)), "/")); - $js->setDefault('{URL_START}', addcslashes($view->url(array($this->progressParameterName => $this->progressParameterRunValue)), "/")); + $js->setDefault('{PANEL_ID}', '#' . $this->_id); + $js->setDefault('{TEXT_ID}', $panel->getDefaultChildTag() . '.' . $panel->progressTextClass); + $js->setDefault('{URL_FINISH}', addcslashes($urlFinish, "/")); + $js->setDefault('{URL_START_RUN}', addcslashes($urlRun, "/")); $js->setDefault('FUNCTION_PREFIX_', $this->getFunctionPrefix()); $panel->append($js); @@ -298,61 +367,111 @@ } /** + * The Zend ProgressBar handles the communication through + * an adapter interface. * * @return Zend_ProgressBar */ public function getProgressBar() { if (! $this->progressBar instanceof Zend_ProgressBar) { - $this->setProgressBar(new Zend_ProgressBar($this->getProgressBarAdapter(), 0, 100)); + $this->setProgressBar(new Zend_ProgressBar($this->getProgressBarAdapter(), 0, 100, $this->_session->getNamespace() . '_pb')); } return $this->progressBar; } /** + * The communication adapter for the ProgressBar. * * @return Zend_ProgressBar_Adapter */ public function getProgressBarAdapter() { - if (! $this->progressBarAdapter instanceof Zend_ProgressBar_Adapter) { - if ($this->method == self::PULL) { + // Does the current adapter accord with the method? + if ($this->progressBarAdapter instanceof Zend_ProgressBar_Adapter) { + if ($this->isPull()) { + if (! $this->progressBarAdapter instanceof Zend_ProgressBar_Adapter_JsPull) { + $this->progressBarAdapter = null; + } + } else { + if (! $this->progressBarAdapter instanceof Zend_ProgressBar_Adapter_JsPush) { + $this->progressBarAdapter = null; + } + } + } else { + $this->progressBarAdapter = null; + } + + // Create when needed + if ($this->progressBarAdapter === null) { + if ($this->isPull()) { $this->setProgressBarAdapter(new Zend_ProgressBar_Adapter_JsPull()); } else { $this->setProgressBarAdapter(new MUtil_ProgressBar_Adapter_JsPush()); - $this->progressBarAdapter->extraPaddingKb = 3; } } + // Check for extra padding + if ($this->progressBarAdapter instanceof MUtil_ProgressBar_Adapter_JsPush) { + $this->progressBarAdapter->extraPaddingKb = $this->extraPushPaddingKb; + } + return $this->progressBarAdapter; } - public function getReport() + /** + * Returns a button that can be clicked to restart the progress bar. + * + * @param mixed $arg_array MUtil_Ra::args() arguments to populate link with + * @return MUtil_Html_HtmlElement + */ + public function getRestartButton($args_array = 'Restart') { - $messages = $this->_session->messages; + $args = MUtil_Ra::args(func_get_args()); + $args['onclick'] = new MUtil_Html_OnClickArrayAttribute( + new MUtil_Html_Raw('if (! this.disabled) {location.href = "'), + new MUtil_Html_HrefArrayAttribute(array($this->progressParameterName => null)), + new MUtil_Html_Raw('";} this.disabled = true; event.cancelBubble=true;')); - return $messages; + return new MUtil_Html_HtmlElement('button', $args); } /** - * Returns true when the parameters passed mean the program has started. + * Returns a link that can be clicked to restart the progress bar. * - * @param Zend_Controller_Request_Abstract $request - * @return boolean + * @param mixed $arg_array MUtil_Ra::args() arguments to populate link with + * @return MUtil_Html_AElement */ - public function hasStarted(Zend_Controller_Request_Abstract $request) + public function getRestartLink($args_array = 'Restart') { - return $request->getParam($this->progressParameterName) === $this->progressParameterRunValue; + $args = MUtil_Ra::args(func_get_args()); + $args['href'] = array($this->progressParameterName => null); + + return new MUtil_Html_AElement($args); } /** - * Return true after commands all have been ran and there was at least one command to run. + * Returns a button that can be clicked to start the progress bar. * + * @param mixed $arg_array MUtil_Ra::args() arguments to populate link with + * @return MUtil_Html_HtmlElement + */ + public function getStartButton($args_array = 'Start') + { + $args = MUtil_Ra::args(func_get_args()); + $args['onclick'] = 'if (! this.disabled) {' . $this->getFunctionPrefix() . 'Start();} this.disabled = true; event.cancelBubble=true;'; + + return new MUtil_Html_HtmlElement('button', $args); + } + + /** + * Return true after commands all have been ran. + * * @return boolean */ public function isFinished() { - return (0 == $this->count()) && ($this->_session->processed > 0); + return $this->_session->finished; } /** @@ -362,18 +481,102 @@ */ public function isLoaded() { - return $this->count() || $this->_session->processed; + return $this->_session->commands || $this->_session->processed; } + /** + * Does the batch use the PULL method for communication. + * + * @return boolean + */ + public function isPull() + { + return $this->method === self::PULL; + } + + /** + * Does the batch use the PUSH method for communication. + * + * @return boolean + */ + public function isPush() + { + return $this->method === self::PUSH; + } + + /** + * Reset and empty the session storage + * + * @return MUtil_Batch_BatchAbstract (continuation pattern) + */ public function reset() { $this->_session->commands = array(); $this->_session->counters = array(); $this->_session->count = 0; + $this->_session->finished = false; $this->_session->messages = array(); $this->_session->processed = 0; + + return $this; } + /** + * Run as much code as possible, but do report back. + * + * Returns true if any output was communicated, i.e. the "normal" + * page should not be displayed. + * + * @param Zend_Controller_Request_Abstract $request + * @return boolean + */ + public function run(Zend_Controller_Request_Abstract $request) + { + // Is there something to run? + if ($this->isFinished() || (! $this->isLoaded())) { + return false; + } + + // Check for run url + if ($request->getParam($this->progressParameterName) === $this->progressParameterRunValue) { + $bar = $this->getProgressBar(); + + $isPull = $this->isPull(); + $reportRun = microtime(true) + ($this->minimalStepDurationMs / 1000); + // error_log('Rep: ' . $reportRun); + while ($this->step()) { + // error_log('Cur: ' . microtime(true) . ' report is '. (microtime(true) > $reportRun ? 'true' : 'false')); + if (microtime(true) > $reportRun) { + // Communicate progress + $percent = round($this->_session->processed / (count($this->_session->commands) + $this->_session->processed) * 100, 2); + + $bar->update($percent, end($this->_session->messages)); + + // INFO: When using PULL $bar->update() should exit the program, + // but just let us make sure. + if ($isPull) { + return true; + } + } + } + if (! $this->_session->commands) { + $this->_session->finished = true; + $bar->finish(); + } + + // There is progressBar output + return true; + } else { + // No ProgressBar output + return false; + } + } + + /** + * Run the whole batch at once, without communicating with a progress bar. + * + * @return int Number of steps taken + */ public function runAll() { while ($this->step()); @@ -382,9 +585,9 @@ } /** - * Name prefix for functions. + * Name prefix for PUSH functions. * - * Set automatically to __CLASS___, use different name + * Set automatically to get_class($this) . '_' $this->_id . '_', use different name * in case of name clashes. * * @param string $prefix @@ -411,7 +614,81 @@ } /** + * Sets the communication method for progress reporting. * + * @param string $method One of the constants of this object + * @return MUtil_Batch_BatchAbstract (continuation pattern) + */ + public function setMethod($method) + { + switch ($method) { + case self::PULL: + case self::PUSH: + $this->method = $method; + return $this; + + default: + throw new MUtil_Batch_BatchException("Invalid batch usage method '$method'."); + } + } + + /** + * Set the communication method used by this batch to PULL. + * + * This is the most stable method as it works independently of + * server settings. Therefore it is the default method. + * + * @return MUtil_Batch_BatchAbstract (continuation pattern) + */ + public function setMethodPull() + { + $this->setMethod(self::PULL); + + return $this; + } + + /** + * Set the communication method used by this batch to PUSH. + * + * I.e. the start page opens an iFrame, the url of the iFrame calls the + * batch with the RUN parameter and the process returns JavaScript tags + * that handle the progress reporting. + * + * This is a very fast and resource inexpensive method for batch processing + * but it is only suitable for short running processes as servers tend to + * cut off http calls that take more than some fixed period of time to run - + * even when those processes keep returning data. + * + * Another problem with this method is buffering, i.e. the tendency of servers + * to wait sending data back until a process has been completed or enough data + * has been send. + * + * E.g. on IIS 7 you have to adjust the file %windir%\System32\inetsrv\config\applicationHost.config + * and add the attribute responseBufferLimit="1024" twice, both to + * ../handlers/add name="PHP_via_FastCGI" and to ../handlers/add name="CGI-exe". + * + * Still the above works only partially, IIS tends to wait longer before sending the + * first batch of data. The trick is to add extra spaces to the output until the + * threshold is reached. This is done by specifying the $extraPaddingKb parameter. + * Just increase it until it works. + * + * @param int $extraPaddingKb + * @return MUtil_Batch_BatchAbstract (continuation pattern) + */ + public function setMethodPush($extraPaddingKb = null) + { + $this->setMethod(self::PUSH); + if ((null !== $extraPaddingKb) && is_numeric($extraPaddingKb)) { + $this->extraPushPaddingKb = $extraPaddingKb; + } + + return $this; + } + + /** + * The Zend ProgressBar handles the communication through + * an adapter interface. + * * @param Zend_ProgressBar $progressBar * @return MUtil_Html_ProgressPanel (continuation pattern) */ @@ -422,6 +699,7 @@ } /** + * The communication adapter for the ProgressBar. * * @param Zend_ProgressBar_Adapter_Interface $adapter * @return MUtil_Html_ProgressPanel (continuation pattern) @@ -458,40 +736,20 @@ } /** - * Returns true when the parameters passed mean the program has started. + * Progress a single step on the command stack * - * @param Zend_Controller_Request_Abstract $request * @return boolean */ - public function showReport(Zend_Controller_Request_Abstract $request) + protected function step() { - return $request->getParam($this->progressParameterName) === $this->progressParameterReportValue; - } - - /** - * Workhorse function that does all the real work. - * - * @return int - */ - public function step() - { - $bar = $this->getProgressBar(); - if (isset($this->_session->commands) && $this->_session->commands) { $command = array_shift($this->_session->commands); $this->_session->processed++; call_user_func_array(array($this, $command['method']), $command['parameters']); - - $percent = round($this->_session->processed / ($this->count() + $this->_session->processed) * 100, 2); - - $bar->update($percent, end($this->_session->messages)); return true; } else { - $bar->finish(); - return false; } - return count($this->_session->commands) > 0; } } Modified: trunk/library/classes/MUtil/Batch/BatchPull.js =================================================================== --- trunk/library/classes/MUtil/Batch/BatchPull.js 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/MUtil/Batch/BatchPull.js 2012-01-19 17:17:39 UTC (rev 412) @@ -5,82 +5,101 @@ // default options options: { - autoStart: false, - // target: the element whose content is replaced - timeout: 2000 - // url: the request url + // finishUrl: the request url + // panelId: text id:, + // runUrl: the request url + // targetId: search for the element whose content is replaced + timeout: 60000 }, _init: function() { - if (this.options.autoStart) { - this.start(); + this.progressTarget = jQuery(this.options.panelId); + if (this.progressTarget.length) { + this.textTarget = this.progressTarget.find(this.options.targetId); + // this.textTarget = this.find(this.options.targetId); + + if (this.textTarget.length) { + this.start(); + } else { + alert('Did not find the text element: "' + this.options.targetId + '" in element id: "' + this.options.panelId + '".'); + } + } else { + alert('Did not find the panel id: "' + this.options.panelId + '".'); } }, complete: function (request, status) { this.request = null; - - // Check for changes - // - if the input field was changed since the last request - // filter() will search on the new value - // - if the input field has not changed, then no new request - // is made. - // this.start(); }, - error: function (request, status) { - console.log(status); - /* if (request.status === 401) { - location.href = location.href; - } // */ + error: function (request, status, error) { + alert('Communication error: ' + status); }, + progressTarget: null, + start: function() { if (this.request == null) { - if (this.options.url) { + if (this.options.runUrl) { var self = this; this.request = jQuery.ajax({ - url: this.options.url, + url: this.options.runUrl, type: "GET", dataType: "json", // data: postData, - error: function(request, status, error) {self.error(request, status);}, + error: function(request, status, error) {self.error(request, status, error);}, complete: function(request, status) {self.complete(request, status);}, success: function(data, status, request) {self.success(data, status, request);} }); - + } else { + alert("No runUrl specified."); } } }, success: function (data, status, request) { - // console.log(stringdata); - // data = jQuery.parseJSON(stringdata); - console.log(data); + // console.log(data); + if (data.finished) { + data.percent = 100; + data.text = false; + } + // For some reason the next two lines are both needed for the code to work + this.progressTarget.progressbar("option", "value", data.percent); + this.progressTarget.progressbar({value: data.percent}); + text = data.percent + '%'; if (data.text) { - text = text + data.text; + text = text + ' ' + data.text; } - jQuery(this.options.target).html(text); + this.textTarget.html(text); + + if (data.finished) { + if (this.options.finishUrl.length > 0) { + location.href = this.options.finishUrl; + } + } else { + this.request = null; + this.start(); + } }, + textTarget: null, + request: null }); -jQuery(document).ready(function() { - jQuery("#{ID}").pullProgressPanel({"url":"{URL_START}","autoStart":__AUTOSTART__,"target":"#{ID} {TEXT_TAG}.{TEXT_CLASS}"}); -}); - -function FUNCTION_PREFIX_Finish() +function FUNCTION_PREFIX_Start() { - main = jQuery("#{ID}"); - main.progressbar( "option", "value", 100); + jQuery("{PANEL_ID}").pullProgressPanel({ + "finishUrl": "{URL_FINISH}", + "panelId": "{PANEL_ID}", + "runUrl": "{URL_START_RUN}", + "targetId": "{TEXT_ID}" + }); +} - inner = main.find('{TEXT_TAG}.{TEXT_CLASS}'); - if (inner) { - inner.empty(); - inner.append('100% Done!'); - } +if (__AUTOSTART__) { + jQuery().ready(FUNCTION_PREFIX_Start()); } Modified: trunk/library/classes/MUtil/Batch/BatchPush.js =================================================================== --- trunk/library/classes/MUtil/Batch/BatchPush.js 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/MUtil/Batch/BatchPush.js 2012-01-19 17:17:39 UTC (rev 412) @@ -3,18 +3,18 @@ { var iFrame = document.createElement('iframe'); iFrame.setAttribute('style', 'position: absolute; left: -100px; top: -100px; width: 10px; height: 10px; overflow: hidden;'); - // iFrame.setAttribute('style', 'position: absolute; left: 0px; top: 0px; width: 100px; height: 100px; overflow: hidden;'); document.getElementsByTagName('body')[0].appendChild(iFrame); - iFrame.src = '{URL_START}'; + iFrame.src = '{URL_START_RUN}'; } function FUNCTION_PREFIX_Update(data) { - main = jQuery("#{ID}"); - main.progressbar( "option", "value", data.percent); + main = jQuery("{PANEL_ID}"); + // For some reason the next two lines are both needed for the code to work + main.progressbar("option", "value", data.percent); main.progressbar({value: data.percent}); - inner = main.find('{TEXT_TAG}.{TEXT_CLASS}'); + inner = main.find('{TEXT_ID}'); if (inner) { text = data.percent + '%'; if (data.text) { @@ -26,10 +26,12 @@ function FUNCTION_PREFIX_Finish() { - main = jQuery("#{ID}"); - main.progressbar( "option", "value", 100); + main = jQuery("{PANEL_ID}"); + // For some reason the next two lines are both needed for the code to work + main.progressbar("option", "value", 100); + main.progressbar({value: 100}); - inner = main.find('{TEXT_TAG}.{TEXT_CLASS}'); + inner = main.find('{TEXT_ID}'); if (inner) { inner.empty(); inner.append('100% Done!'); @@ -41,7 +43,6 @@ } } - if (__AUTOSTART__) { jQuery().ready(FUNCTION_PREFIX_Start()); } \ No newline at end of file Modified: trunk/library/classes/MUtil/Batch/WaitBatch.php =================================================================== --- trunk/library/classes/MUtil/Batch/WaitBatch.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/MUtil/Batch/WaitBatch.php 2012-01-19 17:17:39 UTC (rev 412) @@ -36,8 +36,65 @@ */ /** + * This a an example / test implementation of MUtil_Batch_BatchAbstract. * + * It does nothing but wait, but allows you to test the workings of the + * batch processing in general and the use of a progress panel in general. * + * PULL usage example ($this->view must be a Zend_View) with a nice start button: + * <code> + * $batch = new MUtil_Batch_WaitBatch(); + * if ($batch->run($this->getRequest())) { + * exit; + * } else { + * $html = new MUtil_Html_Sequence(); + * if ($batch->isFinished()) { + * $html->ol($batch->getMessages(true)); + * $html->a(array($batch->progressParameterName => null), 'Restart'); + * } else { + * // Populate the batch (from scratch). + * $batch->reset(); + * $batch->addWaits(4, 2); + * $batch->addWaits(2, 1); + * $batch->addWaitsLater(15, 1); + * $batch->addWait(4, 'That took some time!'); + * $batch->addWait(4, 'So we see the message. :)'); + * $batch->addWaitsLater(1, 2); + * $batch->addWaits(4); + * + * $html->append($batch->getPanel($this->view, $batch->getStartButton('Nice start'))); + * } + * echo $html->render($this->view); + * } + * </code> + * + * PUSH usage example that starts automatically: + * <code> + * $batch = new MUtil_Batch_WaitBatch(); + * $batch->setMethodPush(5); + * $batch->autoStart = true; + * $batch->minimalStepDurationMs = 200; + * + * if ($batch->run($this->getRequest())) { + * exit; + * } else { + * $html = new MUtil_Html_Sequence(); + * if ($batch->isFinished()) { + * $html->ul($batch->getMessages(true)); + * $html->a(array($batch->progressParameterName => null), 'Restart'); + * } else { + * // Populate the batch (from scratch). + * $batch->reset(); + * $batch->addWaitsMs(400, 20); + * $batch->addWaits(2, 1, 'Har har'); + * $batch->addWaitsMs(20, 50); + * + * $html->append($batch->getPanel($this->view)); + * } + * echo $html->render($this->view); + * } + * </code> + * * @package MUtil * @subpackage Batch * @copyright Copyright (c) 2011 Erasmus MC @@ -46,37 +103,156 @@ */ class MUtil_Batch_WaitBatch extends MUtil_Batch_BatchAbstract { - public function addWait($seconds = 1) + /** + * The minimal time used between send progress reports. + * + * This enables quicker processing as multiple steps can be taken in a single + * run(), without the run taking too long to answer. + * + * Set to 0 to report back on each step. + * + * @var int + */ + public $minimalStepDurationMs = 100; + + /** + * Add one second wait command to the command stack. + * + * @param int $seconds + * @param text $message Optional, otherwise the message is the time of wait + * @return MUtil_Batch_WaitBatch (continuation pattern) + */ + public function addWait($seconds = 1, $message = null) { - $this->addStep('waitFor', $seconds); + $this->addStep('waitFor', $seconds, $message); return $this; } - public function addWaits($times, $seconds = 1) + /** + * Add one microsecond wait command to the command stack. + * + * @param int $microsSeconds + * @param text $message Optional, otherwise the message is the time of wait + * @return MUtil_Batch_WaitBatch (continuation pattern) + */ + public function addWaitMs($microsSeconds = 100, $message = null) { + $this->addStep('waitForMs', $microsSeconds, $message); + + return $this; + } + + /** + * Add multiple second wait commands to the command stack. + * + * @param int $times + * @param int $seconds + * @param text $message Optional, otherwise the message is the time of wait + * @return MUtil_Batch_WaitBatch (continuation pattern) + */ + public function addWaits($times, $seconds = 1, $message = null) + { for ($i = 0; $i < $times; $i++) { - $this->addStep('waitFor', $seconds); + $this->addStep('waitFor', $seconds, $message); } return $this; } - public function addWaitsLater($times, $seconds = 1) + /** + * Add multiple microsecond wait commands to the command stack. + * + * @param int $times + * @param int $microsSeconds + * @param text $message Optional, otherwise the message is the time of wait + * @return MUtil_Batch_WaitBatch (continuation pattern) + */ + public function addWaitsMs($times, $microsSeconds = 100, $message = null) { - $this->addStep('addWaits', $times, $seconds); + for ($i = 0; $i < $times; $i++) { + $this->addStep('waitForMs', $microsSeconds, $message); + } return $this; } - public function waitFor($seconds) + /** + * Testing purposes only, this code adds wait commands to the + * command stack during running. + * + * The result is that you may see the percentage done actually + * decrease instead of increase. + * + * @param int $times + * @param int $seconds + * @param text $message Optional, otherwise the message is the time of wait + * @return MUtil_Batch_WaitBatch (continuation pattern) + */ + public function addWaitsLater($times, $seconds = 1, $message = null) { + $this->addStep('addWaits', $times, $seconds, $message); + + return $this; + } + + /** + * Testing purposes only, this code adds wait commands to the + * command stack during running. + * + * The result is that you may see the percentage done actually + * decrease instead of increase. + * + * @param int $times + * @param int $microsSeconds + * @param text $message Optional, otherwise the message is the time of wait + * @return MUtil_Batch_WaitBatch (continuation pattern) + */ + public function addWaitsMsLater($times, $microsSeconds = 100, $message = null) + { + $this->addStep('addWaitsMs', $times, $microsSeconds, $message); + + return $this; + } + + /** + * A step method that just waits for a number of seconds. + * + * @param int $seconds + * @param string $message + */ + protected function waitFor($seconds, $message) + { sleep($seconds); - if ($seconds == 1) { - $this->addMessage("Waited for 1 second."); - } else { - $this->addMessage(sprintf("Waited for %d seconds.", $seconds)); + if (! $message) { + if ($seconds == 1) { + $message = "Waited for 1 second."; + } else { + $message = sprintf("Waited for %d seconds.", $seconds); + } } + $this->addMessage($message); } + + /** + * A step method that just waits for a number of microseconds. + * + * @param int $microsSeconds + * @param string $message + */ + protected function waitForMs($microsSeconds, $message) + { + usleep($microsSeconds); + + if (! $message) { + if ($microsSeconds == 1) { + $message = "Waited for 1 micro second."; + } else { + $message = sprintf("Waited for %.3f seconds.", $microsSeconds / 1000); + } + } + + $this->addMessage($message); + } } Modified: trunk/library/classes/MUtil/ProgressBar/Adapter/JsPush.php =================================================================== --- trunk/library/classes/MUtil/ProgressBar/Adapter/JsPush.php 2012-01-18 15:47:17 UTC (rev 411) +++ trunk/library/classes/MUtil/ProgressBar/Adapter/JsPush.php 2012-01-19 17:17:39 UTC (rev 412) @@ -47,7 +47,7 @@ class MUtil_ProgressBar_Adapter_JsPush extends Zend_ProgressBar_Adapter_JsPush { /** - * The number of bytes to padd in Kilobytes + * The number of bytes to pad in Kilobytes * * This is needed as many servers need extra output passing to avoid buffering. * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |