You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

425 lines
15 KiB

// 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/>.
/**
* This module updates the UI during an asynchronous
* backup or restore process.
*
* @module backup/util/async_backup
* @package core
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.7
*/
define(['jquery', 'core/ajax', 'core/str', 'core/notification', 'core/templates'],
function($, ajax, Str, notification, Templates) {
/**
* Module level constants.
*
* Using var instead of const as ES6 isn't fully supported yet.
*/
var STATUS_EXECUTING = 800;
var STATUS_FINISHED_ERR = 900;
var STATUS_FINISHED_OK = 1000;
/**
* Module level variables.
*/
var Asyncbackup = {};
var checkdelay = 5000; // How often we check for progress updates.
var backupid; // The backup id to get the progress for.
var contextid; // The course this backup progress is for.
var restoreurl; // The URL to view course restores.
var typeid; // The type of operation backup or restore.
var backupintervalid; // The id of the setInterval function.
var allbackupintervalid; // The id of the setInterval function.
/**
* Helper function to update UI components.
*
* @param {string} backupid The id to match elements on.
* @param {number} percentage The completion percentage to apply.
*/
function updateElement(backupid, percentage) {
var percentagewidth = Math.round(percentage) + '%';
var elementbar = $('#' + backupid + '_bar');
var percentagetext = percentage.toFixed(2) + '%';
// Set progress bar percentage indicators
elementbar.attr('aria-valuenow', percentagewidth);
elementbar.css('width', percentagewidth);
elementbar.text(percentagetext);
}
/**
* Update backup table row when an async backup completes.
*
* @param {string} backupid The id to match elements on.
*/
function updateBackupTableRow(backupid) {
var statuscell = $('#' + backupid + '_bar').parent().parent();
var tablerow = statuscell.parent();
var cellsiblings = statuscell.siblings();
var timecell = cellsiblings[1];
var timevalue = $(timecell).text();
var filenamecell = cellsiblings[0];
var filename = $(filenamecell).text();
ajax.call([{
// Get the table data via webservice.
methodname: 'core_backup_get_async_backup_links_backup',
args: {
'filename': filename,
'contextid': contextid
},
}])[0].done(function(response) {
// We have the data now update the UI.
var context = {
filename: filename,
time: timevalue,
size: response.filesize,
fileurl: response.fileurl,
restoreurl: response.restoreurl
};
Templates.render('core/async_backup_progress_row', context).then(function(html, js) {
Templates.replaceNodeContents(tablerow, html, js);
return;
}).fail(function() {
notification.exception(new Error('Failed to load table row'));
return;
});
});
}
/**
* Update restore table row when an async restore completes.
*
* @param {string} backupid The id to match elements on.
*/
function updateRestoreTableRow(backupid) {
var statuscell = $('#' + backupid + '_bar').parent().parent();
var tablerow = statuscell.parent();
var cellsiblings = statuscell.siblings();
var coursecell = cellsiblings[0];
var timecell = cellsiblings[1];
var timevalue = $(timecell).text();
ajax.call([{
// Get the table data via webservice.
methodname: 'core_backup_get_async_backup_links_restore',
args: {
'backupid': backupid,
'contextid': contextid
},
}])[0].done(function(response) {
// We have the data now update the UI.
var resourcename = $(coursecell).text();
var context = {
resourcename: resourcename,
restoreurl: response.restoreurl,
time: timevalue
};
Templates.render('core/async_restore_progress_row', context).then(function(html, js) {
Templates.replaceNodeContents(tablerow, html, js);
return;
}).fail(function() {
notification.exception(new Error('Failed to load table row'));
return;
});
});
}
/**
* Update the Moodle user interface with the progress of
* the backup process.
*
* @param {object} progress The progress and status of the process.
*/
function updateProgress(progress) {
var percentage = progress.progress * 100;
var elementbar = $('#' + backupid + '_bar');
var elementstatus = $('#' + backupid + '_status');
var elementdetail = $('#' + backupid + '_detail');
var elementbutton = $('#' + backupid + '_button');
var stringRequests;
if (progress.status == STATUS_EXECUTING) {
// Process is in progress.
// Add in progress class color to bar
elementbar.addClass('bg-success');
updateElement(backupid, percentage);
// Change heading
var strProcessing = 'async' + typeid + 'processing';
Str.get_string(strProcessing, 'backup').then(function(title) {
elementstatus.text(title);
return title;
}).catch(function() {
notification.exception(new Error('Failed to load string: backup ' + strProcessing));
});
} else if (progress.status == STATUS_FINISHED_ERR) {
// Process completed with error.
// Add in fail class color to bar
elementbar.addClass('bg-danger');
// Remove in progress class color to bar
elementbar.removeClass('bg-success');
updateElement(backupid, 100);
// Change heading and text
var strStatus = 'async' + typeid + 'error';
var strStatusDetail = 'async' + typeid + 'errordetail';
stringRequests = [
{key: strStatus, component: 'backup'},
{key: strStatusDetail, component: 'backup'}
];
Str.get_strings(stringRequests).then(function(strings) {
elementstatus.text(strings[0]);
elementdetail.text(strings[1]);
return strings;
})
.catch(function() {
notification.exception(new Error('Failed to load string'));
return;
});
$('.backup_progress').children('span').removeClass('backup_stage_current');
$('.backup_progress').children('span').last().addClass('backup_stage_current');
// Stop checking when we either have an error or a completion.
clearInterval(backupintervalid);
} else if (progress.status == STATUS_FINISHED_OK) {
// Process completed successfully.
// Add in progress class color to bar
elementbar.addClass('bg-success');
updateElement(backupid, 100);
// Change heading and text
var strComplete = 'async' + typeid + 'complete';
Str.get_string(strComplete, 'backup').then(function(title) {
elementstatus.text(title);
return title;
}).catch(function() {
notification.exception(new Error('Failed to load string: backup ' + strComplete));
});
if (typeid == 'restore') {
ajax.call([{
// Get the table data via webservice.
methodname: 'core_backup_get_async_backup_links_restore',
args: {
'backupid': backupid,
'contextid': contextid
},
}])[0].done(function(response) {
var strDetail = 'async' + typeid + 'completedetail';
var strButton = 'async' + typeid + 'completebutton';
var stringRequests = [
{key: strDetail, component: 'backup', param: response.restoreurl},
{key: strButton, component: 'backup'}
];
Str.get_strings(stringRequests).then(function(strings) {
elementdetail.html(strings[0]);
elementbutton.text(strings[1]);
elementbutton.attr('href', response.restoreurl);
return strings;
})
.catch(function() {
notification.exception(new Error('Failed to load string'));
return;
});
});
} else {
var strDetail = 'async' + typeid + 'completedetail';
var strButton = 'async' + typeid + 'completebutton';
stringRequests = [
{key: strDetail, component: 'backup', param: restoreurl},
{key: strButton, component: 'backup'}
];
Str.get_strings(stringRequests).then(function(strings) {
elementdetail.html(strings[0]);
elementbutton.text(strings[1]);
elementbutton.attr('href', restoreurl);
return strings;
})
.catch(function() {
notification.exception(new Error('Failed to load string'));
return;
});
}
$('.backup_progress').children('span').removeClass('backup_stage_current');
$('.backup_progress').children('span').last().addClass('backup_stage_current');
// Stop checking when we either have an error or a completion.
clearInterval(backupintervalid);
}
}
/**
* Update the Moodle user interface with the progress of
* all the pending processes.
*
* @param {object} progress The progress and status of the process.
*/
function updateProgressAll(progress) {
progress.forEach(function(element) {
var percentage = element.progress * 100;
var backupid = element.backupid;
var elementbar = $('#' + backupid + '_bar');
var type = element.operation;
if (element.status == STATUS_EXECUTING) {
// Process is in element.
// Add in element class color to bar
elementbar.addClass('bg-success');
updateElement(backupid, percentage);
} else if (element.status == STATUS_FINISHED_ERR) {
// Process completed with error.
// Add in fail class color to bar
elementbar.addClass('bg-danger');
elementbar.addClass('complete');
// Remove in element class color to bar
$('#' + backupid + '_bar').removeClass('bg-success');
updateElement(backupid, 100);
} else if (element.status == STATUS_FINISHED_OK) {
// Process completed successfully.
// Add in element class color to bar
elementbar.addClass('bg-success');
elementbar.addClass('complete');
updateElement(backupid, 100);
// We have a successful backup. Update the UI with download and file details.
if (type == 'backup') {
updateBackupTableRow(backupid);
} else {
updateRestoreTableRow(backupid);
}
}
});
}
/**
* Get the progress of the backup process via ajax.
*/
function getBackupProgress() {
ajax.call([{
// Get the backup progress via webservice.
methodname: 'core_backup_get_async_backup_progress',
args: {
'backupids': [backupid],
'contextid': contextid
},
}])[0].done(function(response) {
// We have the progress now update the UI.
updateProgress(response[0]);
});
}
/**
* Get the progress of all backup processes via ajax.
*/
function getAllBackupProgress() {
var backupids = [];
var progressbars = $('.progress').find('.progress-bar').not('.complete');
progressbars.each(function() {
backupids.push((this.id).substring(0, 32));
});
if (backupids.length > 0) {
ajax.call([{
// Get the backup progress via webservice.
methodname: 'core_backup_get_async_backup_progress',
args: {
'backupids': backupids,
'contextid': contextid
},
}])[0].done(function(response) {
updateProgressAll(response);
});
} else {
clearInterval(allbackupintervalid); // No more progress bars to update, stop checking.
}
}
/**
* Get status updates for all backups.
*
* @public
* @param {number} context The context id.
*/
Asyncbackup.asyncBackupAllStatus = function(context) {
contextid = context;
allbackupintervalid = setInterval(getAllBackupProgress, checkdelay);
};
/**
* Get status updates for backup.
*
* @public
* @param {string} backup The backup record id.
* @param {number} context The context id.
* @param {string} restore The restore link.
* @param {string} type The operation type (backup or restore).
*/
Asyncbackup.asyncBackupStatus = function(backup, context, restore, type) {
backupid = backup;
contextid = context;
restoreurl = restore;
if (type == 'backup') {
typeid = 'backup';
} else {
typeid = 'restore';
}
// Remove the links from the progress bar, no going back now.
$('.backup_progress').children('a').removeAttr('href');
// Periodically check for progress updates and update the UI as required.
backupintervalid = setInterval(getBackupProgress, checkdelay);
};
return Asyncbackup;
});