|
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.
|