-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathTransformer.js
More file actions
298 lines (263 loc) · 15.1 KB
/
Transformer.js
File metadata and controls
298 lines (263 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/*!
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
*/
"use strict";
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 launchHandler 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 = 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.
var shouldEnable = fluid.find(settings, function (value, key) {
if (key.endsWith("/enabled")) {
return value;
}
});
if (shouldEnable !== undefined) {
fluid.set(launchHandler, ["settings", "running"], shouldEnable);
}
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);
});
// 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);
});
}
// 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;
}
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;
};
})();