Add requiresVersion property to module API.

This commit is contained in:
Michael Teeuw 2016-10-13 16:42:15 +02:00
parent 15a5e3c779
commit 95edbc16bb
8 changed files with 169 additions and 52 deletions

View File

@ -9,13 +9,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added ### Added
- Finnish translation. - Finnish translation.
- Danish translation. - Danish translation.
- Method to overwrite the module's header. [See documentation.](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader)
- Option to limit access to certain IP addresses based on the value of `ipWhitelist` in the `config.js`, default is access from localhost only (Issue [#456](https://github.com/MichMich/MagicMirror/issues/456)). - Option to limit access to certain IP addresses based on the value of `ipWhitelist` in the `config.js`, default is access from localhost only (Issue [#456](https://github.com/MichMich/MagicMirror/issues/456)).
- Added ability to change the point of time when calendar events get relative. - Added ability to change the point of time when calendar events get relative.
- Add Splash screen on boot. - Add Splash screen on boot.
- Add option to show humidity in currentWeather module. - Add option to show humidity in currentWeather module.
- Add Visibility locking to module system. See [documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
- Add VSCode IntelliSense support. - Add VSCode IntelliSense support.
- Module API: Add Visibility locking to module system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information.
- Module API: Option to define the minimumn MagicMirror verion to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresVersion) for more information.
### Updated ### Updated
- Modified translations for Frysk. - Modified translations for Frysk.

View File

@ -8,6 +8,10 @@
<link rel="stylesheet" type="text/css" href="css/main.css"> <link rel="stylesheet" type="text/css" href="css/main.css">
<link rel="stylesheet" type="text/css" href="fonts/roboto.css"> <link rel="stylesheet" type="text/css" href="fonts/roboto.css">
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. --> <!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
<script type="text/javascript">
var version = "#VERSION#";
</script>
</head> </head>
<body> <body>
<div class="region fullscreen below"><div class="container"></div></div> <div class="region fullscreen below"><div class="container"></div></div>

View File

@ -10,6 +10,10 @@ var Server = require(__dirname + "/server.js");
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js"); var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
var path = require("path"); var path = require("path");
// Get version number.
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
console.log("Starting MagicMirror: v" + global.version);
// The next part is here to prevent a major exception when there // The next part is here to prevent a major exception when there
// is no internet connection. This could probable be solved better. // is no internet connection. This could probable be solved better.
process.on("uncaughtException", function (err) { process.on("uncaughtException", function (err) {
@ -82,6 +86,17 @@ var App = function() {
if (loadModule) { if (loadModule) {
var Module = require(helperPath); var Module = require(helperPath);
var m = new Module(); var m = new Module();
if (m.requiresVersion) {
console.log("Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m.requiresVersion + " - Current version: " + global.version);
if (cmpVersions(global.version, m.requiresVersion) >= 0) {
console.log("Version is ok!");
} else {
console.log("Version is incorrect. Skip module: '" + moduleName + "'");
return;
}
}
m.setName(moduleName); m.setName(moduleName);
m.setPath(path.resolve(moduleFolder)); m.setPath(path.resolve(moduleFolder));
nodeHelpers.push(m); nodeHelpers.push(m);
@ -103,6 +118,28 @@ var App = function() {
console.log("All module helpers loaded."); console.log("All module helpers loaded.");
}; };
/* cmpVersions(a,b)
* Compare two symantic version numbers and return the difference.
*
* argument a string - Version number a.
* argument a string - Version number b.
*/
function cmpVersions(a, b) {
var i, diff;
var regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, "").split(".");
var segmentsB = b.replace(regExStrip0, "").split(".");
var l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}
}
return segmentsA.length - segmentsB.length;
}
/* start(callback) /* start(callback)
* This methods starts the core app. * This methods starts the core app.
* It loads the config, then it loads all modules. * It loads the config, then it loads all modules.

View File

@ -117,9 +117,13 @@ var Loader = (function() {
var afterLoad = function() { var afterLoad = function() {
var moduleObject = Module.create(module.name); var moduleObject = Module.create(module.name);
if (moduleObject) {
bootstrapModule(module, moduleObject, function() { bootstrapModule(module, moduleObject, function() {
callback(); callback();
}); });
} else {
callback();
}
}; };
if (loadedModuleFiles.indexOf(url) !== -1) { if (loadedModuleFiles.indexOf(url) !== -1) {

View File

@ -14,6 +14,9 @@ var Module = Class.extend({
* All methods (and properties) below can be subclassed. * * All methods (and properties) below can be subclassed. *
*********************************************************/ *********************************************************/
// Set the minimum MagicMirror module version for this module.
requiresVersion: "2.0.0",
// Module config defaults. // Module config defaults.
defaults: {}, defaults: {},
@ -27,14 +30,14 @@ var Module = Class.extend({
/* init() /* init()
* Is called when the module is instantiated. * Is called when the module is instantiated.
*/ */
init: function() { init: function () {
//Log.log(this.defaults); //Log.log(this.defaults);
}, },
/* start() /* start()
* Is called when the module is started. * Is called when the module is started.
*/ */
start: function() { start: function () {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
}, },
@ -43,7 +46,7 @@ var Module = Class.extend({
* *
* return Array<String> - An array with filenames. * return Array<String> - An array with filenames.
*/ */
getScripts: function() { getScripts: function () {
return []; return [];
}, },
@ -52,7 +55,7 @@ var Module = Class.extend({
* *
* return Array<String> - An array with filenames. * return Array<String> - An array with filenames.
*/ */
getStyles: function() { getStyles: function () {
return []; return [];
}, },
@ -61,7 +64,7 @@ var Module = Class.extend({
* *
* return Map<String, String> - A map with langKeys and filenames. * return Map<String, String> - A map with langKeys and filenames.
*/ */
getTranslations: function() { getTranslations: function () {
return false; return false;
}, },
@ -71,7 +74,7 @@ var Module = Class.extend({
* *
* return domobject - The dom to display. * return domobject - The dom to display.
*/ */
getDom: function() { getDom: function () {
var nameWrapper = document.createElement("div"); var nameWrapper = document.createElement("div");
var name = document.createTextNode(this.name); var name = document.createTextNode(this.name);
nameWrapper.appendChild(name); nameWrapper.appendChild(name);
@ -95,7 +98,7 @@ var Module = Class.extend({
* *
* return string - The header to display above the header. * return string - The header to display above the header.
*/ */
getHeader: function() { getHeader: function () {
return this.data.header; return this.data.header;
}, },
@ -107,7 +110,7 @@ var Module = Class.extend({
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
* argument sender Module - The module that sent the notification. * argument sender Module - The module that sent the notification.
*/ */
notificationReceived: function(notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if (sender) { if (sender) {
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name); Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
} else { } else {
@ -121,21 +124,21 @@ var Module = Class.extend({
* argument notification string - The identifier of the noitication. * argument notification string - The identifier of the noitication.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
socketNotificationReceived: function(notification, payload) { socketNotificationReceived: function (notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload); Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
}, },
/* suspend() /* suspend()
* This method is called when a module is hidden. * This method is called when a module is hidden.
*/ */
suspend: function() { suspend: function () {
Log.log(this.name + " is suspended."); Log.log(this.name + " is suspended.");
}, },
/* resume() /* resume()
* This method is called when a module is shown. * This method is called when a module is shown.
*/ */
resume: function() { resume: function () {
Log.log(this.name + " is resumed."); Log.log(this.name + " is resumed.");
}, },
@ -148,7 +151,7 @@ var Module = Class.extend({
* *
* argument data obejct - Module data. * argument data obejct - Module data.
*/ */
setData: function(data) { setData: function (data) {
this.data = data; this.data = data;
this.name = data.name; this.name = data.name;
this.identifier = data.identifier; this.identifier = data.identifier;
@ -162,7 +165,7 @@ var Module = Class.extend({
* *
* argument config obejct - Module config. * argument config obejct - Module config.
*/ */
setConfig: function(config) { setConfig: function (config) {
this.config = Object.assign(this.defaults, config); this.config = Object.assign(this.defaults, config);
}, },
@ -170,13 +173,13 @@ var Module = Class.extend({
* Returns a socket object. If it doesn"t exist, it"s created. * Returns a socket object. If it doesn"t exist, it"s created.
* It also registers the notification callback. * It also registers the notification callback.
*/ */
socket: function() { socket: function () {
if (typeof this._socket === "undefined") { if (typeof this._socket === "undefined") {
this._socket = this._socket = new MMSocket(this.name); this._socket = this._socket = new MMSocket(this.name);
} }
var self = this; var self = this;
this._socket.setNotificationCallback(function(notification, payload) { this._socket.setNotificationCallback(function (notification, payload) {
self.socketNotificationReceived(notification, payload); self.socketNotificationReceived(notification, payload);
}); });
@ -190,7 +193,7 @@ var Module = Class.extend({
* *
* return string - File path. * return string - File path.
*/ */
file: function(file) { file: function (file) {
return this.data.path + "/" + file; return this.data.path + "/" + file;
}, },
@ -199,14 +202,14 @@ var Module = Class.extend({
* *
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
loadStyles: function(callback) { loadStyles: function (callback) {
var self = this; var self = this;
var styles = this.getStyles(); var styles = this.getStyles();
var loadNextStyle = function() { var loadNextStyle = function () {
if (styles.length > 0) { if (styles.length > 0) {
var nextStyle = styles[0]; var nextStyle = styles[0];
Loader.loadFile(nextStyle, self, function() { Loader.loadFile(nextStyle, self, function () {
styles = styles.slice(1); styles = styles.slice(1);
loadNextStyle(); loadNextStyle();
}); });
@ -223,14 +226,14 @@ var Module = Class.extend({
* *
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
loadScripts: function(callback) { loadScripts: function (callback) {
var self = this; var self = this;
var scripts = this.getScripts(); var scripts = this.getScripts();
var loadNextScript = function() { var loadNextScript = function () {
if (scripts.length > 0) { if (scripts.length > 0) {
var nextScript = scripts[0]; var nextScript = scripts[0];
Loader.loadFile(nextScript, self, function() { Loader.loadFile(nextScript, self, function () {
scripts = scripts.slice(1); scripts = scripts.slice(1);
loadNextScript(); loadNextScript();
}); });
@ -247,14 +250,14 @@ var Module = Class.extend({
* *
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
loadTranslations: function(callback) { loadTranslations: function (callback) {
var self = this; var self = this;
var translations = this.getTranslations(); var translations = this.getTranslations();
var lang = config.language.toLowerCase(); var lang = config.language.toLowerCase();
// The variable `first` will contain the first // The variable `first` will contain the first
// defined translation after the following line. // defined translation after the following line.
for (var first in translations) {break;} for (var first in translations) { break; }
if (translations) { if (translations) {
var translationFile = translations[lang] || undefined; var translationFile = translations[lang] || undefined;
@ -263,7 +266,7 @@ var Module = Class.extend({
// If a translation file is set, load it and then also load the fallback translation file. // If a translation file is set, load it and then also load the fallback translation file.
// Otherwise only load the fallback translation file. // Otherwise only load the fallback translation file.
if (translationFile !== undefined && translationFile !== translationsFallbackFile) { if (translationFile !== undefined && translationFile !== translationsFallbackFile) {
Translator.load(self, translationFile, false, function() { Translator.load(self, translationFile, false, function () {
Translator.load(self, translationsFallbackFile, true, callback); Translator.load(self, translationsFallbackFile, true, callback);
}); });
} else { } else {
@ -280,7 +283,7 @@ var Module = Class.extend({
* argument key string - The key of the string to translage * argument key string - The key of the string to translage
* argument defaultValue string - The default value if no translation was found. (Optional) * argument defaultValue string - The default value if no translation was found. (Optional)
*/ */
translate: function(key, defaultValue) { translate: function (key, defaultValue) {
return Translator.translate(this, key) || defaultValue || ""; return Translator.translate(this, key) || defaultValue || "";
}, },
@ -289,7 +292,7 @@ var Module = Class.extend({
* *
* argument speed Number - The speed of the animation. (Optional) * argument speed Number - The speed of the animation. (Optional)
*/ */
updateDom: function(speed) { updateDom: function (speed) {
MM.updateDom(this, speed); MM.updateDom(this, speed);
}, },
@ -299,7 +302,7 @@ var Module = Class.extend({
* argument notification string - The identifier of the noitication. * argument notification string - The identifier of the noitication.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
sendNotification: function(notification, payload) { sendNotification: function (notification, payload) {
MM.sendNotification(notification, payload, this); MM.sendNotification(notification, payload, this);
}, },
@ -309,7 +312,7 @@ var Module = Class.extend({
* argument notification string - The identifier of the noitication. * argument notification string - The identifier of the noitication.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
sendSocketNotification: function(notification, payload) { sendSocketNotification: function (notification, payload) {
this.socket().sendNotification(notification, payload); this.socket().sendNotification(notification, payload);
}, },
@ -320,17 +323,17 @@ var Module = Class.extend({
* argument callback function - Called when the animation is done. * argument callback function - Called when the animation is done.
* argument options object - Optional settings for the hide method. * argument options object - Optional settings for the hide method.
*/ */
hide: function(speed, callback, options) { hide: function (speed, callback, options) {
if (typeof callback === "object") { if (typeof callback === "object") {
options = callback; options = callback;
callback = function() {}; callback = function () { };
} }
callback = callback || function() {}; callback = callback || function () { };
options = options || {}; options = options || {};
var self = this; var self = this;
MM.hideModule(self, speed, function() { MM.hideModule(self, speed, function () {
self.suspend(); self.suspend();
callback(); callback();
}, options); }, options);
@ -345,13 +348,13 @@ var Module = Class.extend({
* argument callback function - Called when the animation is done. * argument callback function - Called when the animation is done.
* argument options object - Optional settings for the hide method. * argument options object - Optional settings for the hide method.
*/ */
show: function(speed, callback, options) { show: function (speed, callback, options) {
if (typeof callback === "object") { if (typeof callback === "object") {
options = callback; options = callback;
callback = function() {}; callback = function () { };
} }
callback = callback || function() {}; callback = callback || function () { };
options = options || {}; options = options || {};
this.resume(); this.resume();
@ -361,7 +364,12 @@ var Module = Class.extend({
Module.definitions = {}; Module.definitions = {};
Module.create = function(name) { Module.create = function (name) {
// Make sure module definition is available.
if (!Module.definitions[name]) {
return;
}
//Define the clone method for later use. //Define the clone method for later use.
function cloneObject(obj) { function cloneObject(obj) {
@ -387,7 +395,39 @@ Module.create = function(name) {
}; };
Module.register = function(name, moduleDefinition) { /* cmpVersions(a,b)
* Compare two symantic version numbers and return the difference.
*
* argument a string - Version number a.
* argument a string - Version number b.
*/
function cmpVersions(a, b) {
var i, diff;
var regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, "").split(".");
var segmentsB = b.replace(regExStrip0, "").split(".");
var l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}
}
return segmentsA.length - segmentsB.length;
}
Module.register = function (name, moduleDefinition) {
if (moduleDefinition.requiresVersion) {
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + version);
if (cmpVersions(version, moduleDefinition.requiresVersion) >= 0) {
Log.log("Version is ok!");
} else {
Log.log("Version is incorrect. Skip module: '" + name + "'");
return;
}
}
Log.log("Module registered: " + name); Log.log("Module registered: " + name);
Module.definitions[name] = moduleDefinition; Module.definitions[name] = moduleDefinition;
}; };

View File

@ -11,6 +11,7 @@ var server = require("http").Server(app);
var io = require("socket.io")(server); var io = require("socket.io")(server);
var path = require("path"); var path = require("path");
var ipfilter = require("express-ipfilter").IpFilter; var ipfilter = require("express-ipfilter").IpFilter;
var fs = require("fs");
var Server = function(config, callback) { var Server = function(config, callback) {
console.log("Starting server op port " + config.port + " ... "); console.log("Starting server op port " + config.port + " ... ");
@ -35,8 +36,15 @@ var Server = function(config, callback) {
app.use("/vendor", express.static(path.resolve(__dirname + "/../vendor"))); app.use("/vendor", express.static(path.resolve(__dirname + "/../vendor")));
app.use("/translations", express.static(path.resolve(__dirname + "/../translations"))); app.use("/translations", express.static(path.resolve(__dirname + "/../translations")));
app.get("/version", function(req,res) {
res.send(global.version);
});
app.get("/", function(req, res) { app.get("/", function(req, res) {
res.sendFile(path.resolve(__dirname + "/../index.html")); var html = fs.readFileSync(path.resolve(__dirname + "/../index.html"), {encoding: "utf8"});
html = html.replace("#VERSION#", global.version);
res.send(html);
}); });
if (typeof callback === "function") { if (typeof callback === "function") {

View File

@ -78,6 +78,16 @@ The data object contains additional metadata about the module instance:
####`defaults: {}` ####`defaults: {}`
Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules's configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later. Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules's configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later.
####'requiresVersion:'
*Introduced in version: 2.1.0.*
A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module. Make sure to also set this value in the Node helper.
**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
Example: `"2.1.0"`
### Subclassable module methods ### Subclassable module methods
####`init()` ####`init()`
@ -286,7 +296,7 @@ this.sendSocketNotification('SET_CONFIG', this.config);
####`this.hide(speed, callback, options)` ####`this.hide(speed, callback, options)`
***speed* Number** - Optional (Required when setting callback or options), The speed of the hide animation in milliseconds. ***speed* Number** - Optional (Required when setting callback or options), The speed of the hide animation in milliseconds.
***callback* Function** - Optional, The callback after the hide animation is finished. ***callback* Function** - Optional, The callback after the hide animation is finished.
***options* Function** - Optional, Object with additional options for the hide action (see below). ***options* Function** - Optional, Object with additional options for the hide action (see below). (*Introduced in version: 2.1.0.*)
To hide a module, you can call the `hide(speed, callback)` method. You can call the hide method on the module instance itself using `this.hide()`, but of course you can also hide another module using `anOtherModule.hide()`. To hide a module, you can call the `hide(speed, callback)` method. You can call the hide method on the module instance itself using `this.hide()`, but of course you can also hide another module using `anOtherModule.hide()`.
@ -303,7 +313,7 @@ Possible configurable options:
####`this.show(speed, callback, options)` ####`this.show(speed, callback, options)`
***speed* Number** - Optional (Required when setting callback or options), The speed of the show animation in milliseconds. ***speed* Number** - Optional (Required when setting callback or options), The speed of the show animation in milliseconds.
***callback* Function** - Optional, The callback after the show animation is finished. ***callback* Function** - Optional, The callback after the show animation is finished.
***options* Function** - Optional, Object with additional options for the show action (see below). ***options* Function** - Optional, Object with additional options for the show action (see below). (*Introduced in version: 2.1.0.*)
To show a module, you can call the `show(speed, callback)` method. You can call the show method on the module instance itself using `this.show()`, but of course you can also show another module using `anOtherModule.show()`. To show a module, you can call the `show(speed, callback)` method. You can call the show method on the module instance itself using `this.show()`, but of course you can also show another module using `anOtherModule.show()`.
@ -317,6 +327,9 @@ Possible configurable options:
**Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender). **Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
####Visibility locking ####Visibility locking
(*Introduced in version: 2.1.0.*)
Visiblity locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept: Visiblity locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept:
**Module B asks module A to hide:** **Module B asks module A to hide:**
@ -450,6 +463,16 @@ this.expressApp.use("/" + this.name, express.static(this.path + "/public"));
This is a link to the IO instance. It will allow you to do some Socket.IO magic. In most cases you won't need this, since the Node Helper has a few convenience methods to make this simple. This is a link to the IO instance. It will allow you to do some Socket.IO magic. In most cases you won't need this, since the Node Helper has a few convenience methods to make this simple.
####'requiresVersion:'
*Introduced in version: 2.1.0.*
A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module.
**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
Example: `"2.1.0"`
### Subclassable module methods ### Subclassable module methods
####`init()` ####`init()`

View File

@ -1,6 +1,6 @@
{ {
"name": "magicmirror", "name": "magicmirror",
"version": "2.0.0", "version": "2.1.0",
"description": "A modular interface for smart mirrors.", "description": "A modular interface for smart mirrors.",
"main": "js/electron.js", "main": "js/electron.js",
"scripts": { "scripts": {