Added support for sub-libraries in content upgrade. Also made upgrades async.
parent
8599f2381d
commit
db5243383d
|
@ -1744,7 +1744,7 @@ class H5PCore {
|
||||||
* @param array $versions
|
* @param array $versions
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_upgrades($library, $versions) {
|
public function getUpgrades($library, $versions) {
|
||||||
$upgrades = array();
|
$upgrades = array();
|
||||||
|
|
||||||
foreach ($versions as $upgrade) {
|
foreach ($versions as $upgrade) {
|
||||||
|
|
|
@ -1,5 +1,29 @@
|
||||||
(function ($) {
|
(function ($) {
|
||||||
var info, outData, $container, $throbber, throbberText, majorVersion, minorVersion;
|
var info, $container;
|
||||||
|
|
||||||
|
// TODO: Translate strings!
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Get library info
|
||||||
|
info = H5PIntegration.getLibraryInfo();
|
||||||
|
|
||||||
|
// Get and reset container
|
||||||
|
$container = $('#h5p-admin-container').html('<p>' + info.message + '</p>');
|
||||||
|
|
||||||
|
// Make it possible to select version
|
||||||
|
var $version = $(getVersionSelect(info.versions)).appendTo($container);
|
||||||
|
|
||||||
|
// Add "go" button
|
||||||
|
$('<button/>', {
|
||||||
|
class: 'h5p-admin-upgrade-button',
|
||||||
|
text: info.buttonLabel,
|
||||||
|
click: function () {
|
||||||
|
// Start new content upgrade
|
||||||
|
new ContentUpgrade($version.val());
|
||||||
|
}
|
||||||
|
}).appendTo($container);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate html for version select.
|
* Generate html for version select.
|
||||||
|
@ -19,129 +43,405 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the current batch of parameters.
|
* Private. Helps process each property on the given object asynchronously in serial order.
|
||||||
*
|
*
|
||||||
* @param {Object} params
|
* @param {Object} obj
|
||||||
|
* @param {Function} process
|
||||||
|
* @param {Function} finished
|
||||||
*/
|
*/
|
||||||
var processParameters = function (inData) {
|
var asyncSerial = function (obj, process, finished) {
|
||||||
var upgraded = {};
|
var id, isArray = obj instanceof Array;
|
||||||
|
|
||||||
var i = 0;
|
// Keep track of each property that belongs to this object.
|
||||||
for (var id in inData.params) {
|
if (!isArray) {
|
||||||
if (!inData.params.hasOwnProperty(id)) {
|
var ids = [];
|
||||||
continue;
|
for (id in obj) {
|
||||||
}
|
if (obj.hasOwnProperty(id)) {
|
||||||
|
ids.push(id);
|
||||||
var param = JSON.parse(inData.params[id]);
|
|
||||||
for (var major in H5PUpgrades) {
|
|
||||||
if (!H5PUpgrades.hasOwnProperty(major) || major < info.majorVersion || major > majorVersion) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var minor in H5PUpgrades[major]) {
|
|
||||||
if (!H5PUpgrades[major].hasOwnProperty(major) || minor <= info.minorVersion || minor > minorVersion) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
param = H5PUpgrades[major][minor](param);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
upgraded[id] = JSON.stringify(param);
|
}
|
||||||
|
|
||||||
|
var i = -1; // Keeps track of the current property
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private. Process the next property
|
||||||
|
*/
|
||||||
|
var next = function () {
|
||||||
|
id = isArray ? i : ids[i];
|
||||||
|
process(id, obj[id], check);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private. Check if we're done or have an error.
|
||||||
|
*
|
||||||
|
* @param {String} err
|
||||||
|
*/
|
||||||
|
var check = function (err) {
|
||||||
i++;
|
i++;
|
||||||
$throbber.text(throbberText + Math.round((info.total - inData.left + i) / (info.total / 100)) + ' %') ;
|
if (i === (isArray ? obj.length : ids.length) || err !== undefined) {
|
||||||
}
|
finished(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
outData.params = JSON.stringify(upgraded);
|
check(); // Start
|
||||||
outData.token = inData.token;
|
|
||||||
|
|
||||||
// Get next round of data to process.
|
|
||||||
getParameters();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles errors while processing parameters.
|
* Make it easy to keep track of version details.
|
||||||
*
|
*
|
||||||
* @param {Object} params
|
* @param {String} version
|
||||||
|
* @param {Number} libraryId
|
||||||
|
* @returns {_L1.Version}
|
||||||
*/
|
*/
|
||||||
var process = function (inData) {
|
function Version(version, libraryId) {
|
||||||
// Script is loaded. Start processing.
|
if (libraryId !== undefined) {
|
||||||
try {
|
version = info.versions[libraryId];
|
||||||
processParameters(inData);
|
|
||||||
|
// Public
|
||||||
|
this.libraryId = libraryId;
|
||||||
}
|
}
|
||||||
catch (err) {
|
var versionSplit = version.split('.', 3);
|
||||||
$container.html('An error occurred while processing parameters: ' + err);
|
|
||||||
}
|
// Public
|
||||||
};
|
this.major = versionSplit[0];
|
||||||
|
this.minor = versionSplit[1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public. Custom string for this object.
|
||||||
|
*
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
this.toString = function () {
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the next batch of parameters.
|
* Displays a throbber in the status field.
|
||||||
|
*
|
||||||
|
* @param {String} msg
|
||||||
|
* @returns {_L1.Throbber}
|
||||||
*/
|
*/
|
||||||
var getParameters = function () {
|
function Throbber(msg) {
|
||||||
|
var $throbber = H5PUtils.throbber(msg);
|
||||||
|
$container.html('').append($throbber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes it possible to set the progress.
|
||||||
|
*
|
||||||
|
* @param {String} progress
|
||||||
|
*/
|
||||||
|
this.setProgress = function (progress) {
|
||||||
|
$throbber.text(msg + ' ' + progress);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new content upgrade.
|
||||||
|
*
|
||||||
|
* @param {Number} libraryId
|
||||||
|
* @returns {_L1.ContentUpgrade}
|
||||||
|
*/
|
||||||
|
function ContentUpgrade(libraryId) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Get selected version
|
||||||
|
self.version = new Version(null, libraryId);
|
||||||
|
|
||||||
|
// Create throbber with loading text and progress
|
||||||
|
self.throbber = new Throbber('Upgrading to ' + self.version + '...');
|
||||||
|
|
||||||
|
// Get the next batch
|
||||||
|
self.nextBatch({
|
||||||
|
libraryId: libraryId,
|
||||||
|
token: info.token
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next batch and start processing it.
|
||||||
|
*
|
||||||
|
* @param {Object} outData
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.nextBatch = function (outData) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
$.post(info.url, outData, function (inData) {
|
$.post(info.url, outData, function (inData) {
|
||||||
if (!(inData instanceof Object)) {
|
if (!(inData instanceof Object)) {
|
||||||
// Print errors from backend
|
// Print errors from backend
|
||||||
$container.html(inData);
|
return self.setStatus(inData);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (inData.left === '0') {
|
if (inData.left === 0) {
|
||||||
$container.html(info.done);
|
// Nothing left to process
|
||||||
return;
|
return self.setStatus(info.done);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inData.script !== undefined) {
|
self.left = inData.left;
|
||||||
$.ajax({
|
self.token = inData.token;
|
||||||
dataType: 'script',
|
|
||||||
cache: true,
|
|
||||||
url: inData.script
|
|
||||||
}).done(function () {
|
|
||||||
// Start processing
|
|
||||||
process(inData);
|
|
||||||
}).fail(function () {
|
|
||||||
$container.html('Error: Could not load upgrade script.');
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue processing
|
// Start processing
|
||||||
process(inData);
|
self.processBatch(inData.params);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize
|
/**
|
||||||
$(document).ready(function () {
|
* Set current status message.
|
||||||
// Get library info
|
*
|
||||||
info = H5PIntegration.getLibraryInfo();
|
* @param {String} msg
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.setStatus = function (msg) {
|
||||||
|
$container.html(msg);
|
||||||
|
};
|
||||||
|
|
||||||
// Get and reset container
|
/**
|
||||||
$container = $('#h5p-admin-container').html('<p>' + info.message + '</p>');
|
* Process the given parameters.
|
||||||
|
*
|
||||||
|
* @param {Object} parameters
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.processBatch = function (parameters) {
|
||||||
|
var self = this;
|
||||||
|
var upgraded = {}; // Track upgraded params
|
||||||
|
|
||||||
// Make it possible to select version
|
var current = 0; // Track progress
|
||||||
var $version = $(getVersionSelect(info.versions)).appendTo($container);
|
asyncSerial(parameters, function (id, params, next) {
|
||||||
|
|
||||||
// Add "go" button
|
// Make params possible to work with
|
||||||
$('<button/>', {
|
params = JSON.parse(params);
|
||||||
class: 'h5p-admin-upgrade-button',
|
|
||||||
text: info.buttonLabel,
|
|
||||||
click: function () {
|
|
||||||
outData = {
|
|
||||||
libraryId: $version.val(),
|
|
||||||
token: info.token
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get version
|
// Upgrade this content.
|
||||||
var version = info.versions[outData.libraryId];
|
self.upgrade(info.library.name, new Version(info.library.version), self.version, params, function (err, params) {
|
||||||
var versionLevels = version.split('.', 3);
|
if (err === undefined || err === null) {
|
||||||
majorVersion = versionLevels[0];
|
console.log('Content done', params);
|
||||||
minorVersion = versionLevels[1];
|
upgraded[id] = JSON.stringify(params);
|
||||||
|
|
||||||
throbberText = 'Upgrading to ' + version + '...';
|
current++;
|
||||||
$throbber = H5PUtils.throbber(throbberText);
|
self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %');
|
||||||
$container.html('').append($throbber);
|
}
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
|
||||||
// Start upgrade progress
|
}, function (err) {
|
||||||
getParameters();
|
// Finished with all parameters that came in
|
||||||
|
if (err !== undefined && err !== null) {
|
||||||
|
return self.setStatus('<p>An error occurred while processing parameters:<br/>' + err + '</p>');
|
||||||
}
|
}
|
||||||
}).appendTo($container);
|
|
||||||
});
|
// Save upgraded content and get next round of data to process
|
||||||
|
self.nextBatch({
|
||||||
|
libraryId: self.version.libraryId,
|
||||||
|
token: self.token,
|
||||||
|
params: JSON.stringify(upgraded)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgade the given content.
|
||||||
|
*
|
||||||
|
* @param {String} name
|
||||||
|
* @param {Version} oldVersion
|
||||||
|
* @param {Version} newVersion
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Function} next
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.upgrade = function (name, oldVersion, newVersion, params, next) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
console.log('Upgrading ' + name + ' ' + oldVersion + ' -> ' + newVersion);
|
||||||
|
|
||||||
|
// Load library details and upgrade routines
|
||||||
|
self.loadLibrary(name, newVersion, function (err, library) {
|
||||||
|
if (err) return next(err);
|
||||||
|
|
||||||
|
// Run upgrade routines on params
|
||||||
|
self.processParams(library, oldVersion, newVersion, params, function (err, params) {
|
||||||
|
if (err) return next(err);
|
||||||
|
|
||||||
|
// Check if any of the sub-libraries need upgrading
|
||||||
|
asyncSerial(library.semantics, function (index, field, next) {
|
||||||
|
self.processField(field, params, function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) params = upgradedParams;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
next(err, params);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load library data needed for content upgrade.
|
||||||
|
*
|
||||||
|
* @param {String} name
|
||||||
|
* @param {Version} version
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.loadLibrary = function (name, version, next) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
dataType: 'json',
|
||||||
|
url: info.libraryBaseUrl + '/' + name + '/' + version.major + '/' + version.minor
|
||||||
|
}).fail(function () {
|
||||||
|
next('Could not load data for library ' + name + ' ' + version);
|
||||||
|
}).done(function (library) {
|
||||||
|
if (library.upgradesScript) {
|
||||||
|
self.loadScript(library.upgradesScript, function (err) {
|
||||||
|
if (err) {
|
||||||
|
err = name + ' ' + version + ': ' + err;
|
||||||
|
}
|
||||||
|
next(err, library);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next(null, library);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load script with upgrade hooks.
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.loadScript = function (url, next) {
|
||||||
|
$.ajax({
|
||||||
|
dataType: 'script',
|
||||||
|
cache: true,
|
||||||
|
url: url
|
||||||
|
}).fail(function () {
|
||||||
|
next('Could not load upgrades script.');
|
||||||
|
}).done(function () {
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run upgrade hooks on params.
|
||||||
|
*
|
||||||
|
* @param {Object} library
|
||||||
|
* @param {Version} oldVersion
|
||||||
|
* @param {Version} newVersion
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.processParams = function (library, oldVersion, newVersion, params, next) {
|
||||||
|
if (H5PUpgrades[library.name] === undefined) {
|
||||||
|
// TODO: Add error if we have loaded a script but cannot find the function? Will avoid some errors in the upgrades.js
|
||||||
|
// No upgrade hooks to run. Move on.
|
||||||
|
return next(null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run upgrade hooks. Start by going through major versions
|
||||||
|
asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) {
|
||||||
|
if (major < oldVersion.major || major > newVersion.major) {
|
||||||
|
// Older than the current version or newer than the selected
|
||||||
|
nextMajor();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Go through the minor versions for this major version
|
||||||
|
asyncSerial(minors, function (minor, upgrade, nextMinor) {
|
||||||
|
if (minor <= oldVersion.minor || minor > newVersion.minor) {
|
||||||
|
// Older than or equal to the current version or newer than the selected
|
||||||
|
nextMinor();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We found an upgrade hook, run it
|
||||||
|
upgrade(params, function (err, upgradedParams) {
|
||||||
|
params = upgradedParams;
|
||||||
|
nextMinor(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, nextMajor);
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
next(err, params);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process parameter fields to find and upgrade sub-libraries.
|
||||||
|
*
|
||||||
|
* @param {Object} field
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.processField = function (field, params, next) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
switch (field.type) {
|
||||||
|
case 'library':
|
||||||
|
if (params.library === undefined || params.params === undefined) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for available upgrades
|
||||||
|
var usedLib = params.library.split(' ', 2);
|
||||||
|
for (var i = 0; i < field.options.length; i++) {
|
||||||
|
var availableLib = field.options[i].split(' ', 2);
|
||||||
|
if (availableLib[0] === usedLib[0]) {
|
||||||
|
if (availableLib[1] === usedLib[1]) {
|
||||||
|
return next(); // Same version
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have different versions
|
||||||
|
var usedVer = new Version(usedLib[1]);
|
||||||
|
var availableVer = new Version(availableLib[1]);
|
||||||
|
if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) {
|
||||||
|
return next(); // Larger or same version that's available
|
||||||
|
}
|
||||||
|
|
||||||
|
// A newer version is available, run upgrade.
|
||||||
|
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'group':
|
||||||
|
if (field.fields.length === 1) {
|
||||||
|
var wrap = {};
|
||||||
|
wrap[field.fields[0].name] = params;
|
||||||
|
params = wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all fields in the group
|
||||||
|
asyncSerial(field.fields, function (index, field, next) {
|
||||||
|
self.processField(field, params, function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) params = upgradedParams;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
next(err, params);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'list':
|
||||||
|
if (params[field.name] === undefined) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go trough all params in the list
|
||||||
|
asyncSerial(params[field.name], function (index, params, next) {
|
||||||
|
self.processField(field.field, params, function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) params = upgradedParams;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
next(err, params);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
})(H5P.jQuery);
|
})(H5P.jQuery);
|
Loading…
Reference in New Issue