2015-02-26 16:16:20 +01:00
|
|
|
/*jshint -W083 */
|
|
|
|
var H5PUpgrades = H5PUpgrades || {};
|
|
|
|
|
2015-08-26 15:58:49 +02:00
|
|
|
H5P.ContentUpgradeProcess = (function (Version) {
|
2015-02-26 16:16:20 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @class
|
|
|
|
* @namespace H5P
|
|
|
|
*/
|
2018-04-03 15:17:46 +02:00
|
|
|
function ContentUpgradeProcess(name, oldVersion, newVersion, params, extras, id, loadLibrary, done) {
|
2015-02-26 16:16:20 +01:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// Make params possible to work with
|
|
|
|
try {
|
|
|
|
params = JSON.parse(params);
|
|
|
|
if (!(params instanceof Object)) {
|
|
|
|
throw true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (event) {
|
|
|
|
return done({
|
|
|
|
type: 'errorParamsBroken',
|
|
|
|
id: id
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-03 15:17:46 +02:00
|
|
|
// Make extras possible to work with
|
|
|
|
for (var element in extras) {
|
|
|
|
if (extras.hasOwnProperty(element)) {
|
|
|
|
try {
|
|
|
|
extras[element] = JSON.parse(extras[element]);
|
|
|
|
if (!(extras[element] instanceof Object)) {
|
|
|
|
throw true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (event) {
|
|
|
|
return done({
|
|
|
|
type: 'errorExtrasBroken',
|
|
|
|
id: id
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-26 16:16:20 +01:00
|
|
|
self.loadLibrary = loadLibrary;
|
2018-04-03 15:17:46 +02:00
|
|
|
self.upgrade(name, oldVersion, newVersion, params, extras, function (err, result) {
|
2015-02-26 16:16:20 +01:00
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
|
|
|
|
2018-04-03 15:17:46 +02:00
|
|
|
done(null, JSON.stringify(params), JSON.stringify(extras));
|
2015-02-26 16:16:20 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2018-04-03 15:17:46 +02:00
|
|
|
ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, extras, done) {
|
2015-02-26 16:16:20 +01:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// Load library details and upgrade routines
|
|
|
|
self.loadLibrary(name, newVersion, function (err, library) {
|
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run upgrade routines on params
|
2018-04-03 15:17:46 +02:00
|
|
|
self.processParams(library, oldVersion, newVersion, params, extras, function (err, params, extras) {
|
2015-02-26 16:16:20 +01:00
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if any of the sub-libraries need upgrading
|
|
|
|
asyncSerial(library.semantics, function (index, field, next) {
|
|
|
|
self.processField(field, params[field.name], function (err, upgradedParams) {
|
|
|
|
if (upgradedParams) {
|
|
|
|
params[field.name] = upgradedParams;
|
|
|
|
}
|
|
|
|
next(err);
|
|
|
|
});
|
|
|
|
}, function (err) {
|
2018-04-03 15:17:46 +02:00
|
|
|
done(err, params, extras);
|
2015-02-26 16:16:20 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run upgrade hooks on params.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @param {Object} library
|
|
|
|
* @param {Version} oldVersion
|
|
|
|
* @param {Version} newVersion
|
|
|
|
* @param {Object} params
|
|
|
|
* @param {Function} next
|
|
|
|
*/
|
2018-04-03 15:17:46 +02:00
|
|
|
ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, extras, next) {
|
2015-02-26 16:16:20 +01:00
|
|
|
if (H5PUpgrades[library.name] === undefined) {
|
|
|
|
if (library.upgradesScript) {
|
|
|
|
// Upgrades script should be loaded so the upgrades should be here.
|
|
|
|
return next({
|
|
|
|
type: 'scriptMissing',
|
|
|
|
library: library.name + ' ' + newVersion
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// No upgrades script. 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) {
|
2016-04-04 12:39:57 +02:00
|
|
|
minor =+ minor;
|
2015-02-26 16:16:20 +01:00
|
|
|
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
|
|
|
|
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
|
|
|
|
|
|
|
|
try {
|
2018-04-03 15:17:46 +02:00
|
|
|
unnecessaryWrapper(params, function (err, upgradedParams, upgradedExtras) {
|
2015-02-26 16:16:20 +01:00
|
|
|
params = upgradedParams;
|
2018-04-09 11:43:48 +02:00
|
|
|
/**
|
|
|
|
* "params" (and "extras", e.g. metadata) flow through each update function and are changed
|
|
|
|
* if necessary. Since "extras" was added later and old update functions don't return
|
|
|
|
* it, we need to ignore undefined values here -- or change every single update function
|
|
|
|
* in all content types.
|
|
|
|
*/
|
|
|
|
if (upgradedExtras) {
|
|
|
|
extras = upgradedExtras;
|
|
|
|
}
|
2015-02-26 16:16:20 +01:00
|
|
|
nextMinor(err);
|
2018-04-03 15:17:46 +02:00
|
|
|
}, extras);
|
2015-02-26 16:16:20 +01:00
|
|
|
}
|
|
|
|
catch (err) {
|
2015-04-14 14:01:14 +02:00
|
|
|
if (console && console.log) {
|
|
|
|
console.log("Error", err.stack);
|
|
|
|
console.log("Error", err.name);
|
|
|
|
console.log("Error", err.message);
|
|
|
|
}
|
2015-02-26 16:16:20 +01:00
|
|
|
next(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, nextMajor);
|
|
|
|
}
|
|
|
|
}, function (err) {
|
2018-04-03 15:17:46 +02:00
|
|
|
next(err, params, extras);
|
2015-02-26 16:16:20 +01:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process parameter fields to find and upgrade sub-libraries.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @param {Object} field
|
|
|
|
* @param {Object} params
|
|
|
|
* @param {Function} done
|
|
|
|
*/
|
|
|
|
ContentUpgradeProcess.prototype.processField = function (field, params, done) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (params === undefined) {
|
|
|
|
return done();
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (field.type) {
|
|
|
|
case 'library':
|
|
|
|
if (params.library === undefined || params.params === undefined) {
|
|
|
|
return done();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for available upgrades
|
|
|
|
var usedLib = params.library.split(' ', 2);
|
|
|
|
for (var i = 0; i < field.options.length; i++) {
|
2018-07-24 10:36:43 +02:00
|
|
|
var availableLib = (typeof field.options[i] === 'string') ? field.options[i].split(' ', 2) : field.options[i].name.split(' ', 2);
|
2015-02-26 16:16:20 +01:00
|
|
|
if (availableLib[0] === usedLib[0]) {
|
|
|
|
if (availableLib[1] === usedLib[1]) {
|
|
|
|
return done(); // 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 done(); // Larger or same version that's available
|
|
|
|
}
|
|
|
|
|
2018-04-03 15:17:46 +02:00
|
|
|
// Currently, we only use metadata as additional things that might need change
|
|
|
|
var extras = {
|
|
|
|
metadata: params.metadata
|
|
|
|
};
|
|
|
|
|
2015-02-26 16:16:20 +01:00
|
|
|
// A newer version is available, upgrade params
|
2018-04-03 15:17:46 +02:00
|
|
|
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, extras, function (err, upgraded, extras) {
|
2015-02-26 16:16:20 +01:00
|
|
|
if (!err) {
|
|
|
|
params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
|
|
|
|
params.params = upgraded;
|
2018-04-03 15:17:46 +02:00
|
|
|
if (extras) {
|
|
|
|
if (extras.metadata) {
|
|
|
|
params.metadata = extras.metadata;
|
|
|
|
}
|
|
|
|
}
|
2015-02-26 16:16:20 +01:00
|
|
|
}
|
|
|
|
done(err, params);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
done();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'group':
|
2016-11-29 15:44:32 +01:00
|
|
|
if (field.fields.length === 1 && field.isSubContent !== true) {
|
2015-02-26 16:16:20 +01:00
|
|
|
// Single field to process, wrapper will be skipped
|
|
|
|
self.processField(field.fields[0], params, function (err, upgradedParams) {
|
|
|
|
if (upgradedParams) {
|
|
|
|
params = upgradedParams;
|
|
|
|
}
|
|
|
|
done(err, params);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Go through all fields in the group
|
|
|
|
asyncSerial(field.fields, function (index, subField, next) {
|
2015-04-14 14:01:28 +02:00
|
|
|
var paramsToProcess = params ? params[subField.name] : null;
|
|
|
|
self.processField(subField, paramsToProcess, function (err, upgradedParams) {
|
2015-02-26 16:16:20 +01:00
|
|
|
if (upgradedParams) {
|
|
|
|
params[subField.name] = upgradedParams;
|
|
|
|
}
|
|
|
|
next(err);
|
|
|
|
});
|
2015-08-26 15:58:49 +02:00
|
|
|
|
2015-02-26 16:16:20 +01:00
|
|
|
}, function (err) {
|
|
|
|
done(err, params);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'list':
|
|
|
|
// Go trough all params in the list
|
|
|
|
asyncSerial(params, function (index, subParams, next) {
|
|
|
|
self.processField(field.field, subParams, function (err, upgradedParams) {
|
|
|
|
if (upgradedParams) {
|
|
|
|
params[index] = upgradedParams;
|
|
|
|
}
|
|
|
|
next(err);
|
|
|
|
});
|
|
|
|
}, function (err) {
|
|
|
|
done(err, params);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
done();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helps process each property on the given object asynchronously in serial order.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {Object} obj
|
|
|
|
* @param {Function} process
|
|
|
|
* @param {Function} finished
|
|
|
|
*/
|
|
|
|
var asyncSerial = function (obj, process, finished) {
|
|
|
|
var id, isArray = obj instanceof Array;
|
|
|
|
|
|
|
|
// Keep track of each property that belongs to this object.
|
|
|
|
if (!isArray) {
|
|
|
|
var ids = [];
|
|
|
|
for (id in obj) {
|
|
|
|
if (obj.hasOwnProperty(id)) {
|
|
|
|
ids.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
// We need to use a real async function in order for the stack to clear.
|
|
|
|
setTimeout(function () {
|
|
|
|
i++;
|
|
|
|
if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) {
|
|
|
|
finished(err);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
}, 0);
|
|
|
|
};
|
|
|
|
|
|
|
|
check(); // Start
|
|
|
|
};
|
|
|
|
|
|
|
|
return ContentUpgradeProcess;
|
|
|
|
})(H5P.Version);
|