Merge branch 'MDL-38190-master' of git://github.com/sammarshallou/moodle

Conflicts:
	backup/import.php
	theme/bootstrapbase/style/moodle.css
This commit is contained in:
Eloy Lafuente (stronk7)
2013-09-02 16:56:50 +02:00
20 changed files with 1103 additions and 19 deletions
+13 -5
View File
@@ -89,11 +89,6 @@ if (!($bc = backup_ui::load_controller($backupid))) {
}
$backup = new backup_ui($bc);
$backup->process();
if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
$backup->execute();
} else {
$backup->save_controller();
}
$PAGE->set_title($heading.': '.$backup->get_stage_name());
$PAGE->set_heading($heading);
@@ -104,6 +99,19 @@ echo $OUTPUT->header();
if ($backup->enforce_changed_dependencies()) {
debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
}
if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
// Display an extra progress bar so that we can show the progress first.
echo html_writer::start_div('', array('id' => 'executionprogress'));
echo $renderer->progress_bar($backup->get_progress_bar());
$backup->get_controller()->set_progress(new core_backup_display_progress());
$backup->execute();
echo html_writer::end_div();
echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
} else {
$backup->save_controller();
}
echo $renderer->progress_bar($backup->get_progress_bar());
echo $backup->display($renderer);
$backup->destroy();
@@ -64,6 +64,11 @@ class backup_controller extends backup implements loggable {
protected $destination; // Destination chain object (fs_moodle, fs_os, db, email...)
protected $logger; // Logging chain object (moodle, inline, fs, db, syslog)
/**
* @var core_backup_progress Progress reporting object.
*/
protected $progress;
protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
/**
@@ -109,6 +114,10 @@ class backup_controller extends backup implements loggable {
// Default logger chain (based on interactive/execution)
$this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->backupid);
// By default there is no progress reporter. Interfaces that wish to
// display progress must set it.
$this->progress = new core_backup_null_progress();
// Instantiate the output_controller singleton and active it if interactive and inmediate
$oc = output_controller::get_instance();
if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
@@ -302,6 +311,25 @@ class backup_controller extends backup implements loggable {
return $this->logger;
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return core_backup_progress Progress reporting object
*/
public function get_progress() {
return $this->progress;
}
/**
* Sets the progress reporter.
*
* @param core_backup_progress $progress Progress reporting object
*/
public function set_progress(core_backup_progress $progress) {
$this->progress = $progress;
}
/**
* Executes the backup
* @return void Throws and exception of completes
@@ -57,6 +57,11 @@ class restore_controller extends backup implements loggable {
protected $logger; // Logging chain object (moodle, inline, fs, db, syslog)
/**
* @var core_backup_progress Progress reporting object.
*/
protected $progress;
protected $checksum; // Cache @checksumable results for lighter @is_checksum_correct() uses
/**
@@ -101,6 +106,10 @@ class restore_controller extends backup implements loggable {
// Default logger chain (based on interactive/execution)
$this->logger = backup_factory::get_logger_chain($this->interactive, $this->execution, $this->restoreid);
// By default there is no progress reporter. Interfaces that wish to
// display progress must set it.
$this->progress = new core_backup_null_progress();
// Instantiate the output_controller singleton and active it if interactive and inmediate
$oc = output_controller::get_instance();
if ($this->interactive == backup::INTERACTIVE_YES && $this->execution == backup::EXECUTION_INMEDIATE) {
@@ -300,6 +309,25 @@ class restore_controller extends backup implements loggable {
return $this->logger;
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return core_backup_progress Progress reporting object
*/
public function get_progress() {
return $this->progress;
}
/**
* Sets the progress reporter.
*
* @param core_backup_progress $progress Progress reporting object
*/
public function set_progress(core_backup_progress $progress) {
$this->progress = $progress;
}
public function execute_plan() {
// Basic/initial prevention against time/memory limits
set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+20 -1
View File
@@ -90,11 +90,25 @@ if ($backup->get_stage() == backup_ui::STAGE_CONFIRMATION) {
// If it's the final stage process the import
if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
echo $OUTPUT->header();
// Display an extra progress bar so that we can show the current stage.
echo html_writer::start_div('', array('id' => 'executionprogress'));
echo $renderer->progress_bar($backup->get_progress_bar());
// Start the progress display - we split into 2 chunks for backup and restore.
$progress = new core_backup_display_progress();
$progress->start_progress('', 2);
$backup->get_controller()->set_progress($progress);
// First execute the backup
$backup->execute();
$backup->destroy();
unset($backup);
// Note that we've done that progress.
$progress->progress(1);
// Check whether the backup directory still exists. If missing, something
// went really wrong in backup, throw error. Note that backup::MODE_IMPORT
// backups don't store resulting files ever
@@ -106,6 +120,7 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
// Prepare the restore controller. We don't need a UI here as we will just use what
// ever the restore has (the user has just chosen).
$rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_YES, backup::MODE_IMPORT, $USER->id, $restoretarget);
$rc->set_progress($progress);
// Convert the backup if required.... it should NEVER happed
if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
$rc->convert();
@@ -140,8 +155,12 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
// Delete the temp directory now
fulldelete($tempdestination);
// All progress complete. Hide progress area.
$progress->end_progress();
echo html_writer::end_div();
echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
// Display a notification and a continue button
echo $OUTPUT->header();
if ($warnings) {
echo $OUTPUT->box_start();
echo $OUTPUT->notification(get_string('warning'), 'notifywarning');
@@ -157,6 +157,11 @@ class backup_final_task extends backup_task {
$this->built = true;
}
public function get_weight() {
// The final task takes ages, so give it 20 times the weight of a normal task.
return 20;
}
// Protected API starts here
/**
+20 -12
View File
@@ -44,18 +44,6 @@ if ($stage & restore_ui::STAGE_CONFIRM + restore_ui::STAGE_DESTINATION) {
}
$outcome = $restore->process();
if (!$restore->is_independent()) {
if ($restore->get_stage() == restore_ui::STAGE_PROCESS && !$restore->requires_substage()) {
try {
$restore->execute();
} catch(Exception $e) {
$restore->cleanup();
throw $e;
}
} else {
$restore->save_controller();
}
}
$heading = $course->fullname;
$PAGE->set_title($heading.': '.$restore->get_stage_name());
@@ -67,6 +55,26 @@ echo $OUTPUT->header();
if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
}
if (!$restore->is_independent()) {
if ($restore->get_stage() == restore_ui::STAGE_PROCESS && !$restore->requires_substage()) {
try {
// Display an extra progress bar so that we can show the progress first.
echo html_writer::start_div('', array('id' => 'executionprogress'));
echo $renderer->progress_bar($restore->get_progress_bar());
$restore->get_controller()->set_progress(new core_backup_display_progress());
$restore->execute();
echo html_writer::end_div();
echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
} catch(Exception $e) {
$restore->cleanup();
throw $e;
}
} else {
$restore->save_controller();
}
}
echo $renderer->progress_bar($restore->get_progress_bar());
echo $restore->display($renderer);
$restore->destroy();
+3
View File
@@ -71,6 +71,9 @@ require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
@@ -60,6 +60,9 @@ require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
+10
View File
@@ -87,6 +87,16 @@ class backup_plan extends base_plan implements loggable {
return $this->controller->get_logger();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return core_backup_progress Progress reporting object
*/
public function get_progress() {
return $this->controller->get_progress();
}
public function is_excluding_activities() {
return $this->excludingdactivities;
}
+21
View File
@@ -158,12 +158,33 @@ abstract class base_plan implements checksumable, executable {
if (!$this->built) {
throw new base_plan_exception('base_plan_not_built');
}
// Calculate the total weight of all tasks and start progress tracking.
$progress = $this->get_progress();
$totalweight = 0;
foreach ($this->tasks as $task) {
$totalweight += $task->get_weight();
}
$progress->start_progress($this->get_name(), $totalweight);
// Build and execute all tasks.
foreach ($this->tasks as $task) {
$task->build();
$task->execute();
}
// Finish progress tracking.
$progress->end_progress();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return core_backup_progress Progress reporting object
*/
public abstract function get_progress();
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
+33
View File
@@ -67,6 +67,17 @@ abstract class base_task implements checksumable, executable, loggable {
return $this->settings;
}
/**
* Returns the weight of this task, an approximation of the amount of time
* it will take. By default this value is 1. It can be increased for longer
* tasks.
*
* @return int Weight
*/
public function get_weight() {
return 1;
}
public function get_setting($name) {
// First look in task settings
$result = null;
@@ -111,6 +122,16 @@ abstract class base_task implements checksumable, executable, loggable {
return $this->plan->get_logger();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return core_backup_progress Progress reporting object
*/
public function get_progress() {
return $this->plan->get_progress();
}
public function log($message, $level, $a = null, $depth = null, $display = false) {
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
}
@@ -149,6 +170,13 @@ abstract class base_task implements checksumable, executable, loggable {
if ($this->executed) {
throw new base_task_exception('base_task_already_executed', $this->name);
}
// Starts progress based on the weight of this task and number of steps.
$progress = $this->get_progress();
$progress->start_progress($this->get_name(), count($this->steps), $this->get_weight());
$done = 0;
// Execute all steps.
foreach ($this->steps as $step) {
$result = $step->execute();
// If step returns array, it will be forwarded to plan
@@ -156,11 +184,16 @@ abstract class base_task implements checksumable, executable, loggable {
if (is_array($result) and !empty($result)) {
$this->add_result($result);
}
$done++;
$progress->progress($done);
}
// Mark as executed if any step has been executed
if (!empty($this->steps)) {
$this->executed = true;
}
// Finish progress for this task.
$progress->end_progress();
}
/**
+10
View File
@@ -94,6 +94,16 @@ class restore_plan extends base_plan implements loggable {
return $this->controller->get_logger();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return core_backup_progress Progress reporting object
*/
public function get_progress() {
return $this->controller->get_progress();
}
public function get_info() {
return $this->controller->get_info();
}
+4
View File
@@ -34,6 +34,10 @@ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
class mock_base_plan extends base_plan {
public function build() {
}
public function get_progress() {
return null;
}
}
/**
@@ -0,0 +1,136 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Progress handler that uses a standard Moodle progress bar to display
* progress. The Moodle progress bar cannot show indeterminate progress,
* so we do extra output in addition to the bar.
*
* @package core_backup
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_backup_display_progress extends core_backup_progress {
/**
* @var int Number of wibble states (state0...stateN-1 classes in CSS)
*/
const WIBBLE_STATES = 13;
/**
* @var progress_bar Current progress bar.
*/
private $bar;
private $lastwibble, $currentstate = 0, $direction = 1;
/**
* @var bool True to display names
*/
protected $displaynames = false;
/**
* Constructs the progress reporter. This will output HTML code for the
* progress bar, and an indeterminate wibbler below it.
*
* @param bool $startnow If true, outputs HTML immediately.
*/
public function __construct($startnow = true) {
if ($startnow) {
$this->start_html();
}
}
/**
* By default, the progress section names do not display because (in backup)
* these are usually untranslated and incomprehensible. To make them
* display, call this method.
*
* @param bool $displaynames True to display names
*/
public function set_display_names($displaynames = true) {
$this->displaynames = $displaynames;
}
/**
* Starts to output progress.
*
* Called in constructor and in update_progress if required.
*
* @throws coding_exception If already started
*/
public function start_html() {
if ($this->bar) {
throw new coding_exception('Already started');
}
$this->bar = new progress_bar();
$this->bar->create();
echo html_writer::start_div('wibbler');
}
/**
* Finishes output. (Progress can begin again later if there are more
* calls to update_progress.)
*
* Automatically called from update_progress when progress finishes.
*/
public function end_html() {
// Finish progress bar.
$this->bar->update_full(100, '');
$this->bar = null;
// End wibbler div.
echo html_writer::end_div();
}
public function update_progress() {
// If finished...
if (!$this->is_in_progress_section()) {
if ($this->bar) {
$this->end_html();
}
} else {
if (!$this->bar) {
$this->start_html();
}
// In case of indeterminate or small progress, update the wibbler
// (up to once per second).
if (time() != $this->lastwibble) {
$this->lastwibble = time();
echo html_writer::div('', 'wibble state' . $this->currentstate);
// Go on to next colour.
$this->currentstate += $this->direction;
if ($this->currentstate < 0 || $this->currentstate >= self::WIBBLE_STATES) {
$this->direction = -$this->direction;
$this->currentstate += 2 * $this->direction;
}
}
// Get progress.
list ($min, $max) = $this->get_progress_proportion_range();
// Update progress bar.
$message = '';
if ($this->displaynames) {
$message = $this->get_current_description();
}
$this->bar->update_full($min * 100, $message);
// Flush output.
flush();
}
}
}
@@ -0,0 +1,28 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Progress handler that ignores progress entirely.
*
* @package core_backup
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_backup_null_progress extends core_backup_progress {
public function update_progress() {
// Do nothing.
}
}
@@ -0,0 +1,307 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for handling progress information during a backup and restore.
*
* Subclasses should generally override the current_progress function which
* summarises all progress information.
*
* @package core_backup
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class core_backup_progress {
/**
* @var int Constant indicating that the number of progress calls is unknown.
*/
const INDETERMINATE = -1;
/**
* @var int The number of seconds that can pass without progress() calls.
*/
const TIME_LIMIT_WITHOUT_PROGRESS = 120;
/**
* @var int Time of last progress call.
*/
protected $lastprogresstime;
/**
* @var int Number of progress calls (restricted to ~ 1/second).
*/
protected $count;
/**
* @var array Array of progress descriptions for each stack level.
*/
protected $descriptions = array();
/**
* @var array Array of maximum progress values for each stack level.
*/
protected $maxes = array();
/**
* @var array Array of current progress values.
*/
protected $currents = array();
/**
* @var int Array of counts within parent progress entry (ignored for first)
*/
protected $parentcounts = array();
/**
* Marks the start of an operation that will display progress.
*
* This can be called multiple times for nested progress sections. It must
* be paired with calls to end_progress.
*
* The progress maximum may be INDETERMINATE if the current operation has
* an unknown number of steps. (This is default.)
*
* Calling this function will always result in a new display, so this
* should not be called exceedingly frequently.
*
* When it is complete by calling end_progress, each start_progress section
* automatically adds progress to its parent, as defined by $parentcount.
*
* @param string $description Description to display
* @param int $max Maximum value of progress for this section
* @param int $parentcount How many progress points this section counts for
* @throws coding_exception If max is invalid
*/
public function start_progress($description, $max = self::INDETERMINATE,
$parentcount = 1) {
if ($max != self::INDETERMINATE && $max <= 0) {
throw new coding_exception(
'start_progress() max value cannot be zero or negative');
}
if ($parentcount < 1) {
throw new coding_exception(
'start_progress() parent progress count must be at least 1');
}
if (!empty($this->descriptions)) {
$prevmax = end($this->maxes);
if ($prevmax !== self::INDETERMINATE) {
$prevcurrent = end($this->currents);
if ($prevcurrent + $parentcount > $prevmax) {
throw new coding_exception(
'start_progress() parent progress would exceed max');
}
}
} else {
if ($parentcount != 1) {
throw new coding_exception(
'start_progress() progress count must be 1 when no parent');
}
}
$this->descriptions[] = $description;
$this->maxes[] = $max;
$this->currents[] = 0;
$this->parentcounts[] = $parentcount;
$this->update_progress();
$lastprogresstime = $this->get_time();
}
/**
* Marks the end of an operation that will display progress.
*
* This must be paired with each start_progress call.
*
* If there is a parent progress section, its progress will be increased
* automatically to reflect the end of the child section.
*
* @throws coding_exception If progress hasn't been started
*/
public function end_progress() {
if (!count($this->descriptions)) {
throw new coding_exception('end_progress() without start_progress()');
}
array_pop($this->descriptions);
array_pop($this->maxes);
array_pop($this->currents);
$parentcount = array_pop($this->parentcounts);
if (!empty($this->descriptions)) {
$lastmax = end($this->maxes);
if ($lastmax != self::INDETERMINATE) {
$lastvalue = end($this->currents);
$this->currents[key($this->currents)] = $lastvalue + $parentcount;
}
}
$this->update_progress();
}
/**
* Indicates that progress has occurred.
*
* The progress value should indicate the total progress so far, from 0
* to the value supplied for $max (inclusive) in start_progress.
*
* You do not need to call this function for every value. It is OK to skip
* values. It is also OK to call this function as often as desired; it
* doesn't do anything if called more than once per second.
*
* It must be INDETERMINATE if start_progress was called with $max set to
* INDETERMINATE. Otherwise it must not be indeterminate.
*
* @param int $progress Progress so far
* @throws coding_exception If progress value is invalid
*/
public function progress($progress = self::INDETERMINATE) {
// Ignore too-frequent progress calls (more than once per second).
$now = $this->get_time();
if ($now === $this->lastprogresstime) {
return;
}
// Check we are inside a progress section.
$max = end($this->maxes);
if ($max === false) {
throw new coding_exception(
'progress() without start_progress');
}
// Check and apply new progress.
if ($progress === self::INDETERMINATE) {
// Indeterminate progress.
if ($max !== self::INDETERMINATE) {
throw new coding_exception(
'progress() INDETERMINATE, expecting value');
}
} else {
// Determinate progress.
$current = end($this->currents);
if ($max === self::INDETERMINATE) {
throw new coding_exception(
'progress() with value, expecting INDETERMINATE');
} else if ($progress < 0 || $progress > $max) {
throw new coding_exception(
'progress() value out of range');
} else if ($progress < $current) {
throw new coding_Exception(
'progress() value may not go backwards');
}
$this->currents[key($this->currents)] = $progress;
}
// Update progress.
$this->count++;
$this->lastprogresstime = $now;
set_time_limit(self::TIME_LIMIT_WITHOUT_PROGRESS);
$this->update_progress();
}
/**
* Gets time (this is provided so that unit tests can override it).
*
* @return int Current system time
*/
protected function get_time() {
return time();
}
/**
* Called whenever new progress should be displayed.
*/
protected abstract function update_progress();
/**
* @return bool True if currently inside a progress section
*/
public function is_in_progress_section() {
return !empty($this->descriptions);
}
/**
* @return string Current progress section description
*/
public function get_current_description() {
$description = end($this->descriptions);
if ($description === false) {
throw new coding_exception('Not inside progress section');
}
return $description;
}
/**
* Obtains current progress in a way suitable for drawing a progress bar.
*
* Progress is returned as a minimum and maximum value. If there is no
* indeterminate progress, these values will be identical. If there is
* intermediate progress, these values can be different. (For example, if
* the top level progress sections is indeterminate, then the values will
* always be 0.0 and 1.0.)
*
* @return array Minimum and maximum possible progress proportions
*/
public function get_progress_proportion_range() {
// If there is no progress underway, we must have finished.
if (empty($this->currents)) {
return array(1.0, 1.0);
}
$count = count($this->currents);
$min = 0.0;
$max = 1.0;
for ($i = 0; $i < $count; $i++) {
// Get max value at that section - if it's indeterminate we can tell
// no more.
$sectionmax = $this->maxes[$i];
if ($sectionmax === self::INDETERMINATE) {
return array($min, $max);
}
// Special case if current value is max (this should only happen
// just before ending a section).
$sectioncurrent = $this->currents[$i];
if ($sectioncurrent === $sectionmax) {
return array($max, $max);
}
// Using the current value at that section, we know we are somewhere
// between 'current' and the next 'current' value which depends on
// the parentcount of the nested section (if any).
$newmin = ($sectioncurrent / $sectionmax) * ($max - $min) + $min;
$nextcurrent = $sectioncurrent + 1;
if ($i + 1 < $count) {
$weight = $this->parentcounts[$i + 1];
$nextcurrent = $sectioncurrent + $weight;
}
$newmax = ($nextcurrent / $sectionmax) * ($max - $min) + $min;
$min = $newmin;
$max = $newmax;
}
// If there was nothing indeterminate, we use the min value as current.
return array($min, $min);
}
/**
* Obtains current indeterminate progress in a way suitable for adding to
* the progress display.
*
* This returns the number of indeterminate calls (at any level) during the
* lifetime of this progress reporter, whether or not there is a current
* indeterminate step. (The number will not be ridiculously high because
* progress calls are limited to one per second.)
*
* @return int Number of indeterminate progress calls
*/
public function get_progress_count() {
return $this->count;
}
}
@@ -0,0 +1,363 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for the progress classes.
*
* @package core_backup
* @category phpunit
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Include all the needed stuff.
global $CFG;
require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
/**
* Progress tests.
*/
class backup_progress_testcase extends basic_testcase {
/**
* Tests for basic use with simple numeric progress.
*/
public function test_basic() {
$progress = new core_backup_mock_progress();
// Check values of empty progress things.
$this->assertFalse($progress->is_in_progress_section());
// Start progress counting, check basic values and check that update
// gets called.
$progress->start_progress('hello', 10);
$this->assertTrue($progress->was_update_called());
$this->assertTrue($progress->is_in_progress_section());
$this->assertEquals('hello', $progress->get_current_description());
// Check numeric position and indeterminate count.
$this->assert_min_max(0.0, 0.0, $progress);
$this->assertEquals(0, $progress->get_progress_count());
// Make some progress and check that the time limit gets added.
$progress->step_time();
$progress->progress(2);
$this->assertTrue($progress->was_update_called());
$this->assertEquals(120, ini_get('max_execution_time'));
// Check the new value.
$this->assert_min_max(0.2, 0.2, $progress);
// Do another progress run at same time, it should be ignored.
$progress->progress(3);
$this->assertFalse($progress->was_update_called());
$this->assert_min_max(0.2, 0.2, $progress);
// End the section. This should cause an update.
$progress->end_progress();
$this->assertTrue($progress->was_update_called());
// Because there are no sections left open, it thinks we finished.
$this->assert_min_max(1.0, 1.0, $progress);
// There was 1 progress call.
$this->assertEquals(1, $progress->get_progress_count());
// Clear the time limit, otherwise phpunit complains.
set_time_limit(0);
}
/**
* Tests progress that is nested and/or indeterminate.
*/
public function test_nested() {
// Outer progress goes from 0 to 10.
$progress = new core_backup_mock_progress();
$progress->start_progress('hello', 10);
// Get up to 4, check position.
$progress->step_time();
$progress->progress(4);
$this->assert_min_max(0.4, 0.4, $progress);
$this->assertEquals('hello', $progress->get_current_description());
// Now start indeterminate progress.
$progress->start_progress('world');
$this->assert_min_max(0.4, 0.5, $progress);
$this->assertEquals('world', $progress->get_current_description());
// Do some indeterminate progress and count it (once per second).
$progress->step_time();
$progress->progress();
$this->assertEquals(2, $progress->get_progress_count());
$progress->progress();
$this->assertEquals(2, $progress->get_progress_count());
$progress->step_time();
$progress->progress();
$this->assertEquals(3, $progress->get_progress_count());
$this->assert_min_max(0.4, 0.5, $progress);
// Exit the indeterminate section.
$progress->end_progress();
$this->assert_min_max(0.5, 0.5, $progress);
$progress->step_time();
$progress->progress(7);
$this->assert_min_max(0.7, 0.7, $progress);
// Enter a numbered section (this time with a range of 5).
$progress->start_progress('frogs', 5);
$this->assert_min_max(0.7, 0.7, $progress);
$progress->step_time();
$progress->progress(1);
$this->assert_min_max(0.72, 0.72, $progress);
$progress->step_time();
$progress->progress(3);
$this->assert_min_max(0.76, 0.76, $progress);
// Now enter another indeterminate section.
$progress->start_progress('and');
$this->assert_min_max(0.76, 0.78, $progress);
// Make some progress, should increment indeterminate count.
$progress->step_time();
$progress->progress();
$this->assertEquals(7, $progress->get_progress_count());
// Enter numbered section, won't make any difference to values.
$progress->start_progress('zombies', 2);
$progress->step_time();
$progress->progress(1);
$this->assert_min_max(0.76, 0.78, $progress);
$this->assertEquals(8, $progress->get_progress_count());
// Leaving it will make no difference too.
$progress->end_progress();
// Leaving the indeterminate section will though.
$progress->end_progress();
$this->assert_min_max(0.78, 0.78, $progress);
// Leave the two numbered sections.
$progress->end_progress();
$this->assert_min_max(0.8, 0.8, $progress);
$progress->end_progress();
$this->assertFalse($progress->is_in_progress_section());
set_time_limit(0);
}
/**
* Tests the feature for 'weighting' nested progress.
*/
public function test_nested_weighted() {
$progress = new core_backup_mock_progress();
$progress->start_progress('', 10);
// First nested child has 2 units of its own and is worth 1 unit.
$progress->start_progress('', 2);
$progress->step_time();
$progress->progress(1);
$this->assert_min_max(0.05, 0.05, $progress);
$progress->end_progress();
$this->assert_min_max(0.1, 0.1, $progress);
// Next child has 2 units of its own but is worth 3 units.
$progress->start_progress('weighted', 2, 3);
$progress->step_time();
$progress->progress(1);
$this->assert_min_max(0.25, 0.25, $progress);
$progress->end_progress();
$this->assert_min_max(0.4, 0.4, $progress);
// Next indeterminate child is worth 6 units.
$progress->start_progress('', core_backup_progress::INDETERMINATE, 6);
$progress->step_time();
$progress->progress();
$this->assert_min_max(0.4, 1.0, $progress);
$progress->end_progress();
$this->assert_min_max(1.0, 1.0, $progress);
set_time_limit(0);
}
/**
* I had some issues with real use in backup/restore, this test is intended
* to be similar.
*/
public function test_realistic() {
$progress = new core_backup_mock_progress();
$progress->start_progress('parent', 100);
$progress->start_progress('child', 1);
$progress->progress(1);
$this->assert_min_max(0.01, 0.01, $progress);
$progress->end_progress();
$this->assert_min_max(0.01, 0.01, $progress);
// Clear the time limit, otherwise phpunit complains.
set_time_limit(0);
}
/**
* Tests for any exceptions due to invalid calls.
*/
public function test_exceptions() {
$progress = new core_backup_mock_progress();
// Check errors when empty.
try {
$progress->progress();
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
}
try {
$progress->end_progress();
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~without start_progress~', $e->getMessage()));
}
try {
$progress->get_current_description();
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~Not inside progress~', $e->getMessage()));
}
try {
$progress->start_progress('', 1, 7);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~must be 1~', $e->getMessage()));
}
// Check invalid start (0).
try {
$progress->start_progress('hello', 0);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~cannot be zero or negative~', $e->getMessage()));
}
// Indeterminate when value expected.
$progress->start_progress('hello', 10);
try {
$progress->progress(core_backup_progress::INDETERMINATE);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~expecting value~', $e->getMessage()));
}
// Value when indeterminate expected.
$progress->start_progress('hello');
try {
$progress->progress(4);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~expecting INDETERMINATE~', $e->getMessage()));
}
// Illegal values.
$progress->start_progress('hello', 10);
try {
$progress->progress(-2);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
}
try {
$progress->progress(11);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~out of range~', $e->getMessage()));
}
// You are allowed two with the same value...
$progress->progress(4);
$progress->step_time();
$progress->progress(4);
$progress->step_time();
// ...but not to go backwards.
try {
$progress->progress(3);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~backwards~', $e->getMessage()));
}
// When you go forward, you can't go further than there is room.
try {
$progress->start_progress('', 1, 7);
$this->fail();
} catch (coding_exception $e) {
$this->assertEquals(1, preg_match('~would exceed max~', $e->getMessage()));
}
// Clear the time limit, otherwise phpunit complains.
set_time_limit(0);
}
/**
* Checks the current progress values are as expected.
*
* @param number $min Expected min progress
* @param number $max Expected max progress
* @param core_backup_mock_progress $progress
*/
private function assert_min_max($min, $max, core_backup_mock_progress $progress) {
$this->assertEquals(array($min, $max),
$progress->get_progress_proportion_range());
}
}
/**
* Helper class that records when update_progress is called and allows time
* stepping.
*/
class core_backup_mock_progress extends core_backup_progress {
private $updatecalled = false;
private $time = 1;
/**
* Checks if update was called since the last call to this function.
*
* @return boolean True if update was called
*/
public function was_update_called() {
if ($this->updatecalled) {
$this->updatecalled = false;
return true;
}
return false;
}
/**
* Steps the current time by 1 second.
*/
public function step_time() {
$this->time++;
}
protected function update_progress() {
$this->updatecalled = true;
}
protected function get_time() {
return $this->time;
}
}
+15
View File
@@ -542,6 +542,21 @@ body.tag .managelink {padding: 5px;}
.path-backup .fitemtitle .iconlarge.icon-post { padding-left: 6px; }
.path-backup.dir-rtl .fitemtitle .iconlarge.icon-post { padding-right: 6px; padding-right: 0; }
.path-backup .fitem .smallicon { vertical-align: text-bottom; }
.path-backup .wibbler { width: 500px; margin: 0 auto 10px; border-bottom: 1px solid black; border-right: 1px solid black; border-left: 1px solid black; position: relative; min-height: 4px;}
.path-backup .wibbler .wibble { position: absolute; left: 0; right: 0; top: 0; height: 4px; }
.path-backup .wibbler .state0 { background: #eee; }
.path-backup .wibbler .state1 { background: #ddd; }
.path-backup .wibbler .state2 { background: #ccc; }
.path-backup .wibbler .state3 { background: #bbb; }
.path-backup .wibbler .state4 { background: #aaa; }
.path-backup .wibbler .state5 { background: #999; }
.path-backup .wibbler .state6 { background: #888; }
.path-backup .wibbler .state7 { background: #777; }
.path-backup .wibbler .state8 { background: #666; }
.path-backup .wibbler .state9 { background: #555; }
.path-backup .wibbler .state10 { background: #444; }
.path-backup .wibbler .state11 { background: #333; }
.path-backup .wibbler .state12 { background: #222; }
/**
* Web Service
@@ -162,3 +162,58 @@
background-color: #eee;
padding: 3px;
}
.path-backup .wibbler {
width: 500px;
margin: 0 auto 10px;
border-bottom: 1px solid black;
border-right: 1px solid black;
border-left: 1px solid black;
position: relative;
min-height: 4px;
}
.path-backup .wibbler .wibble {
position: absolute;
left: 0;
right: 0;
top: 0;
height: 4px;
}
.path-backup .wibbler .state0 {
background: #eee;
}
.path-backup .wibbler .state1 {
background: #ddd;
}
.path-backup .wibbler .state2 {
background: #ccc;
}
.path-backup .wibbler .state3 {
background: #bbb;
}
.path-backup .wibbler .state4 {
background: #aaa;
}
.path-backup .wibbler .state5 {
background: #999;
}
.path-backup .wibbler .state6 {
background: #888;
}
.path-backup .wibbler .state7 {
background: #777;
}
.path-backup .wibbler .state8 {
background: #666;
}
.path-backup .wibbler .state9 {
background: #555;
}
.path-backup .wibbler .state10 {
background: #444;
}
.path-backup .wibbler .state11 {
background: #333;
}
.path-backup .wibbler .state12 {
background: #222;
}
File diff suppressed because one or more lines are too long