gpii-universal
Version:
Cross platform, core components of the GPII personalization infrastructure.
294 lines (258 loc) • 15.4 kB
JavaScript
/*!
GPII Settings Transformer
Copyright 2012 OCAD University
Copyright 2012 Antranig Basman
Copyright 2013-2014 Raising the Floor
Licensed under the New BSD license. You may not use this file except in
compliance with this License.
The research leading to these results has received funding from the European Union's
Seventh Framework Programme (FP7/2007-2013) under grant agreement no. 289016.
You may obtain a copy of the License at
https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
;
var fluid = fluid || require("infusion"),
$ = fluid.registerNamespace("jQuery"),
gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.transformer");
(function () {
/**
* Helper function to compute settings paths of a settings object.
*
* Setting paths extracted from input settings argument include:
* 1. All individual keys in the settings object;
* 2. All concatenated settings paths of the top level setting with the 2nd level settings provided
* the 2nd level path doesn't start with "http://registry.gpii.net/";
* 3. All returned settings paths must start with "http://registry.gpii.net/" to extract both common terms
* and application specific terms.
*
* For example, if the input settings is:
* {
* "http://registry.gpii.net/applications/com.microsoft.windows.magnifier": {
* "invertColours": true
* },
* "http://registry.gpii.net/applications/com.microsoft.windows.magnifier": {
* "http://registry.gpii.net/common/invertColours": true
* }
* }
* The returned array will look like:
* [
* "http://registry.gpii.net/applications/com.microsoft.windows.magnifier",
* "http://registry\\.gpii\\.net/applications/com\\.microsoft\\.windows\\.magnifier/invertColours",
* "http://registry.gpii.net/applications/com.microsoft.windows.magnifier",
* "http://registry.gpii.net/common/invertColours"
* ]
*
* @param {Object} settings - The demanded settings. Usually a value of
* matchMakerOutput.inferredConfiguration.(context).applications.(solutionId).settings
* @return {Boolean} - An array of settings paths.
*/
gpii.transformer.computeDemandedSettingsPaths = function (settings) {
var settingsPaths = [];
fluid.each(settings, function (value, key) {
if (key.startsWith("http://registry.gpii.net/")) {
settingsPaths.push(fluid.pathUtil.composeSegments(key));
}
if (fluid.isPlainObject(value, true)) {
fluid.each(value, function (subValue, subKey) {
if (subKey.startsWith("http://registry.gpii.net/")) {
settingsPaths.push(fluid.pathUtil.composeSegments(subKey));
} else {
settingsPaths.push(fluid.pathUtil.composeSegments(key, subKey));
}
});
}
});
return settingsPaths;
};
/**
* Helper function to find out if settings paths extracted from a given settings has a match in:
* 1. The capabilities;
* 2. The capabilitiesTransformations for a settingsHandler block has inputPaths
* @param {Array} capabilities - The capabilities block for a settingsHandler.
* @param {Object} capabilitiesTransformations - The capabilitiesTransformations block for a settingsHandler.
* @param {Object} settings - The settings.
* Usually from matchMakerOutput.inferredConfiguration.(context).applications.(solutionId).settings.
* @return {Boolean} - True if an inputPath is found, otherwise, return false.
*/
gpii.transformer.hasSupportedSettings = function (capabilities, capabilitiesTransformations, settings) {
if (!capabilitiesTransformations || !settings) {
return false;
}
// 1. Extract settings paths from the demanded settings
var demandedSettingPaths = gpii.transformer.computeDemandedSettingsPaths(settings);
// 2. Find settings paths supported by the settings handler
var capabilitiesArray = fluid.copy(fluid.makeArray(capabilities));
var transformationSupportedSettingPaths = fluid.model.transform.collectInputPaths(capabilitiesTransformations);
var supportedSettingPaths = capabilitiesArray.concat(transformationSupportedSettingPaths);
// 3. Find out if one or more actual settings are supported by the settings handler
var originalLength = supportedSettingPaths.length;
gpii.arrayDifference(supportedSettingPaths, demandedSettingPaths);
return supportedSettingPaths.length < originalLength;
};
/**
* Helper function for gpii.transformer.configurationToSettings. Used to modify the settings handler
* blocks to be in the format required (i.e. including the application specific settings to be
* set) by the actual settings handlers. This modifies the block in place - it has already been copied by the
* the caller
*
* @param {Object} settingsHandler - The settingsHandler information from the solutions registry to be transformed - MODIFIED IN PLACE.
* @param {Object} oneUserSolution - The user preferences related to this solution and settings handler.
* @param {Object} solutionId - The ID of the solution for which the settingsHandler is related.
* @return {Object} - The modified settingsHandler object - ready to be passed to the lifecycle manager
*/
gpii.transformer.transformOneSettingsHandler = function (settingsHandler, oneUserSolution, solutionId) {
var settings = fluid.copy(oneUserSolution.settings["http://registry.gpii.net/applications/" + solutionId]);
// extract any common terms that were defined in the application block and delete them from the settings block
var scopedCommon = {};
fluid.each(settings, function (val, key) {
if (key.startsWith("http://registry.gpii.net/common/")) {
scopedCommon[key] = fluid.copy(val); // add to list of scoped common terms
delete settings[key]; // remove from the settings
}
});
// filter to only keep relevant application specific settings
settings = gpii.settingsHandlers.filterSupportedSettings(settings, settingsHandler.supportedSettings);
var hasSupportedSettings = gpii.transformer.hasSupportedSettings(settingsHandler.capabilities, settingsHandler.capabilitiesTransformations, oneUserSolution.settings);
if (hasSupportedSettings) {
var inferred = fluid.model.transformWithRules(oneUserSolution.settings, settingsHandler.capabilitiesTransformations);
var inferredScoped = (scopedCommon === undefined || $.isEmptyObject(scopedCommon)) ? {} : fluid.model.transformWithRules(scopedCommon, settingsHandler.capabilitiesTransformations);
settings = fluid.extend(true, {}, inferred, inferredScoped, settings);
}
settingsHandler.settings = settings || {};
return settingsHandler;
};
/**
* Modify a solution registry entry to clean up settingsHandlers that have empty settings block.
* 1. Remove settingsHandlers that have empty settings block.
* 2. Remove corresponding settingsHandler definitions from these solution registry paths:
* "update", "configure", "restore"
*
* @param {Object} oneSolution - A solution registry entry that is in transform - MODIFIED IN PLACE.
*/
gpii.transformer.adjustSettingsHandlerRelatedData = function (oneSolution) {
fluid.each(oneSolution.settingsHandlers, function (settingsHandler, key) {
if ($.isEmptyObject(settingsHandler.settings)) {
delete oneSolution.settingsHandlers[key];
var elementToRemove = "settings." + key;
gpii.arrayDifference(oneSolution.update, elementToRemove);
gpii.arrayDifference(oneSolution.configure, elementToRemove);
gpii.arrayDifference(oneSolution.restore, elementToRemove);
}
});
};
/**
* Helper function for `gpii.transformer.configurationToSettings`. Used to modify the launch handler
* blocks to be in the format required (i.e. setting the "settings.running" to true/false based on the
* corresponding /enabled preference value). This modifies the block in place - it has already been copied by the
* the caller.
*
* @param {Object} launchHandler - The launchHandler information from the solutions registry to be transformed - MODIFIED IN PLACE.
* @param {Object} oneUserSolution - The user preferences related to this solution and launch handler.
* @return {Object} - The modified launchHandler object - ready to be passed to the lifecycle manager
*/
gpii.transformer.transformOneLaunchHandler = function (launchHandler, oneUserSolution) {
var settings = fluid.copy(oneUserSolution.settings);
// launchHandler's "settings.running" value is set based on these rules:
// 1. If /enabled preference was provided, set the value to /enabled preference value;
// 2. If /enabled preference was not provided, set the value to `true` to launch the app for handling "settingsHandler".
// launchHandler's "settings.running" is set in all cases to ensure at user key-in when the key-in action needs
// to be merged with the default snapshot for "reset all" (see function gpii.lifecycleManager.userLogonHandling.startLifecycle()
// and GPII-3434), the structure of lifecycleInstructions for both key-in action and default snapshot match each other
// so that the launchHandler's "settings.running" value from the key-in will override the one from the default snapshot.
fluid.each(settings, function (value, key) {
fluid.set(launchHandler, ["settings", "running"], key.endsWith("/enabled") ? value : true);
});
return launchHandler;
};
/**
* Converts a system configuration (output from matchmaker output) to a format readable by the
* lifecycle manager. This includes translation of common terms to application specific settings.
* The following rules apply when doing this translation:
* - the translation is based on the transformations in the solutionsRegistry entry
* - if a common term transforms into a setting which is already specific as application specific
* in the configuration argument, the application specific takes priority. In other words:
* if the configuration contains both an application specific and (transformable) common
* term for some application setting, the application specific one will stay and the
* transformed will be discarded.
*
* @param {Object} configuration - An element of the matchmaker output payload for a specific preferences set
* @param {Object} solutionsRegistryEntries - A section of the solutions registry database, filtered for configured solutions (currently only done by OS and device context).
* @return {Object} An object isomorphic to the "configuration" with settings blocks transformed and evaluated - this is ready to be
* dispatched to settings handlers.
*/
gpii.transformer.configurationToSettings = function (configuration, solutionsRegistryEntries) {
var togo = fluid.transform(configuration.applications, function (oneUserSolution, solutionId) {
var oneSolution = fluid.copy(solutionsRegistryEntries[solutionId]);
if (oneSolution === undefined) {
fluid.log("The configuration to be applied contains an entry for " + solutionId +
" which was not found in the solutionsRegistry. Aborting configuration");
return undefined;
}
delete oneSolution.contexts; // solutions registry entries should include this as "old device reporter context info"
oneSolution.settingsHandlers = fluid.transform(oneSolution.settingsHandlers, function (settingsHandler) {
return gpii.transformer.transformOneSettingsHandler(settingsHandler, oneUserSolution, solutionId);
});
// Remove settingsHandlers that have empty settings.
gpii.transformer.adjustSettingsHandlerRelatedData(oneSolution);
// Remove settingsHandlers path itself if it doesn't contain any settingsHandler.
if ($.isEmptyObject(oneSolution.settingsHandlers)) {
delete oneSolution.settingsHandlers;
}
// Handle /enabled preferences to launch or stop solutions
if (oneSolution.launchHandlers) {
oneSolution.launchHandlers = fluid.transform(oneSolution.launchHandlers, function (launchHandler) {
return gpii.transformer.transformOneLaunchHandler(launchHandler, oneUserSolution, solutionId);
});
}
oneSolution.active = oneUserSolution.active; // copy over active directive for later use
return oneSolution;
});
// ensure no empty entries exist in the settings payload
for (var i in togo) {
if (togo[i] === undefined) {
delete togo[i];
}
}
return togo;
};
// TODO: This transform has no tests
fluid.defaults("gpii.transformer.booleanToNumber", {
gradeNames: "fluid.standardTransformFunction"
});
gpii.transformer.booleanToNumber = function (value) {
return value ? 1 : 0;
};
/**
* timeInRange is implemented specifically for checking whether an input time falls in between
* two given times. The valid and required inputs, besides the standard input/inputPath are `to`
* and `from`. All inputs should be strings of the type "hh:mm" (eg. "17:30"). The range wraps
* midnight, so given a `from` of "20:00" and a `to` of `"08:00"` all the following inputs would
* result in true: "20:00", 23:00", "02:45".
*/
fluid.defaults("gpii.transformer.timeInRange", {
gradeNames: "fluid.standardTransformFunction"
});
gpii.transformer.timeInRange = function (value, transformSpec) {
var timeInRange = gpii.transformer.timeInRange;
var currentTime = timeInRange.timeParser(value);
var fromTime = timeInRange.timeParser(transformSpec.from);
var toTime = timeInRange.timeParser(transformSpec.to);
if (timeInRange.isEarlier(toTime, fromTime)) { // if time wraps
return (timeInRange.isEarlier(fromTime, currentTime) || timeInRange.isEarlier(currentTime, toTime));
} else {
return (timeInRange.isEarlier(fromTime, currentTime) && timeInRange.isEarlier(currentTime, toTime));
}
};
gpii.transformer.timeInRange.timeParser = function (time) {
time = time.split(":");
return {
hours: parseInt(time[0], 10),
minutes: parseInt(time[1], 10)
};
};
// checks whether 'first' is earlier than 'second' - returns true if so
gpii.transformer.timeInRange.isEarlier = function (first, second) {
return (60 * (first.hours - second.hours) + first.minutes - second.minutes) < 0;
};
})();