AutorÃa | Ultima modificación | Ver Log |
// 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 core_backup/async_backup* @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 checkdelayoriginal = 15000; // This is the default time to use.var checkdelay = 15000; // How often we should check for progress updates.var checkdelaymultipler = 1.5; // If a request fails this multiplier will be used to increase the checkdelay valuevar 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.var allcopyintervalid; // The id of the setInterval function.var timeout = 2000; // Timeout for ajax requests./*** Helper function to update UI components.** @param {string} backupid The id to match elements on.* @param {string} type The type of operation, backup or restore.* @param {number} percentage The completion percentage to apply.*/function updateElement(backupid, type, percentage) {var percentagewidth = Math.round(percentage) + '%';var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];var percentagetext = percentage.toFixed(2) + '%';// Set progress bar percentage indicatorselementbar.setAttribute('aria-valuenow', percentagewidth);elementbar.style.width = percentagewidth;elementbar.innerHTML = percentagetext;}/*** Updates the interval we use to check for backup progress.** @param {Number} intervalid The id of the interval* @param {Function} callback The function to use in setInterval* @param {Number} value The specified interval (in milliseconds)* @returns {Number}*/function updateInterval(intervalid, callback, value) {clearInterval(intervalid);return setInterval(callback, value);}/*** 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,'backupid': backupid},}])[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 copy table row when an course copy completes.** @param {string} backupid The id to match elements on.*/function updateCopyTableRow(backupid) {var elementbar = document.querySelectorAll("[data-restoreid=" + CSS.escape(backupid) + "]")[0];var restorecourse = elementbar.closest('tr').children[1];var coursename = restorecourse.innerHTML;var courselink = document.createElement('a');var elementbarparent = elementbar.closest('td');var operation = elementbarparent.previousElementSibling;// Replace the prgress bar.Str.get_string('complete').then(function(content) {operation.innerHTML = content;return;}).catch(function() {notification.exception(new Error('Failed to load string: complete'));return;});Templates.render('core/async_copy_complete_cell', {}).then(function(html, js) {Templates.replaceNodeContents(elementbarparent, html, js);return;}).fail(function() {notification.exception(new Error('Failed to load table cell'));return;});// Update the destination course name to a link to that course.ajax.call([{methodname: 'core_backup_get_async_backup_links_restore',args: {'backupid': backupid,'contextid': 0},}])[0].done(function(response) {courselink.setAttribute('href', response.restoreurl);courselink.innerHTML = coursename;restorecourse.innerHTML = null;restorecourse.appendChild(courselink);return;}).fail(function() {notification.exception(new Error('Failed to update 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 type = 'backup';var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];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.classList.add('bg-success');updateElement(backupid, type, percentage);// Change heading.var strProcessing = 'async' + typeid + 'processing';Str.get_string(strProcessing, 'backup').then(function(title) {elementstatus.text(title);return;}).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.classList.add('bg-danger');// Remove in progress class color to bar.elementbar.classList.remove('bg-success');updateElement(backupid, type, 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;}).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 barelementbar.classList.add('bg-success');updateElement(backupid, type, 100);// Change heading and textvar strComplete = 'async' + typeid + 'complete';Str.get_string(strComplete, 'backup').then(function(title) {elementstatus.text(title);return;}).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;}).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;}).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 for backup and restore operations.** @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 type = element.operation;var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];if (element.status == STATUS_EXECUTING) {// Process is in element.// Add in element class color to barelementbar.classList.add('bg-success');updateElement(backupid, type, percentage);} else if (element.status == STATUS_FINISHED_ERR) {// Process completed with error.// Add in fail class color to barelementbar.classList.add('bg-danger');elementbar.classList.add('complete');// Remove in element class color to barelementbar.classList.remove('bg-success');updateElement(backupid, type, 100);} else if (element.status == STATUS_FINISHED_OK) {// Process completed successfully.// Add in element class color to barelementbar.classList.add('bg-success');elementbar.classList.add('complete');updateElement(backupid, type, 100);// We have a successful backup. Update the UI with download and file details.if (type == 'backup') {updateBackupTableRow(backupid);} else {updateRestoreTableRow(backupid);}}});}/*** Update the Moodle user interface with the progress of* all the pending processes for copy operations.** @param {object} progress The progress and status of the process.*/function updateProgressCopy(progress) {progress.forEach(function(element) {var percentage = element.progress * 100;var backupid = element.backupid;var type = element.operation;var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];if (type == 'restore') {let restorecell = elementbar.closest('tr').children[3];Str.get_string('restore').then(function(content) {restorecell.innerHTML = content;return;}).catch(function() {notification.exception(new Error('Failed to load string: restore'));});}if (element.status == STATUS_EXECUTING) {// Process is in element.// Add in element class color to barelementbar.classList.add('bg-success');updateElement(backupid, type, percentage);} else if (element.status == STATUS_FINISHED_ERR) {// Process completed with error.// Add in fail class color to barelementbar.classList.add('bg-danger');elementbar.classList.add('complete');// Remove in element class color to barelementbar.classList.remove('bg-success');updateElement(backupid, type, 100);} else if ((element.status == STATUS_FINISHED_OK) && (type == 'restore')) {// Process completed successfully.// Add in element class color to barelementbar.classList.add('bg-success');elementbar.classList.add('complete');updateElement(backupid, type, 100);// We have a successful copy. Update the UI link to copied course.updateCopyTableRow(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},}], true, true, false, timeout)[0].done(function(response) {// We have the progress now update the UI.updateProgress(response[0]);checkdelay = checkdelayoriginal;backupintervalid = updateInterval(backupintervalid, getBackupProgress, checkdelayoriginal);}).fail(function() {checkdelay = checkdelay * checkdelaymultipler;backupintervalid = updateInterval(backupintervalid, getBackupProgress, checkdelay);});}/*** 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},}], true, true, false, timeout)[0].done(function(response) {updateProgressAll(response);checkdelay = checkdelayoriginal;allbackupintervalid = updateInterval(allbackupintervalid, getAllBackupProgress, checkdelayoriginal);}).fail(function() {checkdelay = checkdelay * checkdelaymultipler;allbackupintervalid = updateInterval(allbackupintervalid, getAllBackupProgress, checkdelay);});} else {clearInterval(allbackupintervalid); // No more progress bars to update, stop checking.}}/*** Get the progress of all copy processes via ajax.*/function getAllCopyProgress() {var copyids = [];var progressbars = $('.progress').find('.progress-bar[data-operation][data-backupid][data-restoreid]').not('.complete');progressbars.each(function() {let progressvars = {'backupid': this.dataset.backupid,'restoreid': this.dataset.restoreid,'operation': this.dataset.operation,};copyids.push(progressvars);});if (copyids.length > 0) {ajax.call([{// Get the copy progress via webservice.methodname: 'core_backup_get_copy_progress',args: {'copies': copyids},}], true, true, false, timeout)[0].done(function(response) {updateProgressCopy(response);checkdelay = checkdelayoriginal;allcopyintervalid = updateInterval(allcopyintervalid, getAllCopyProgress, checkdelayoriginal);}).fail(function() {checkdelay = checkdelay * checkdelaymultipler;allcopyintervalid = updateInterval(allcopyintervalid, getAllCopyProgress, checkdelay);});} else {clearInterval(allcopyintervalid); // 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 all course copies.** @public*/Asyncbackup.asyncCopyAllStatus = function() {allcopyintervalid = setInterval(getAllCopyProgress, 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;});