2016-04-08 22:16:22 +02:00
/* Magic Mirror
* The Core App (Server)
*
2020-04-28 23:05:28 +02:00
* By Michael Teeuw https://michaelteeuw.nl
2016-04-08 22:16:22 +02:00
* MIT Licensed.
*/
var fs = require ( "fs" );
2020-05-11 23:12:50 +02:00
var path = require ( "path" );
var Log = require ( __dirname + "/logger.js" );
2016-04-08 22:16:22 +02:00
var Server = require ( __dirname + "/server.js" );
2017-02-28 01:41:21 -03:00
var Utils = require ( __dirname + "/utils.js" );
2016-04-08 22:16:22 +02:00
var defaultModules = require ( __dirname + "/../modules/default/defaultmodules.js" );
2020-01-01 19:17:23 +01:00
// Alias modules mentioned in package.js under _moduleAliases.
require ( "module-alias/register" );
2016-10-13 16:42:15 +02:00
// Get version number.
global . version = JSON . parse ( fs . readFileSync ( "package.json" , "utf8" )). version ;
2020-05-11 07:25:42 +02:00
Log . log ( "Starting MagicMirror: v" + global . version );
2016-10-13 16:42:15 +02:00
2016-12-27 14:31:35 -03:00
// global absolute root path
global . root_path = path . resolve ( __dirname + "/../" );
2017-01-24 02:59:20 -03:00
if ( process . env . MM_CONFIG_FILE ) {
global . configuration_file = process . env . MM_CONFIG_FILE ;
}
2017-02-24 18:52:33 -03:00
// FIXME: Hotfix Pull Request
// https://github.com/MichMich/MagicMirror/pull/673
2017-01-31 21:58:46 -03:00
if ( process . env . MM_PORT ) {
global . mmPort = process . env . MM_PORT ;
}
2016-04-19 11:25:50 +02:00
// The next part is here to prevent a major exception when there
// is no internet connection. This could probable be solved better.
2016-05-03 19:09:38 -04:00
process . on ( "uncaughtException" , function ( err ) {
2020-06-01 16:40:20 +02:00
Log . error ( "Whoops! There was an uncaught exception..." );
2020-05-11 07:25:42 +02:00
Log . error ( err );
2020-06-01 16:40:20 +02:00
Log . error ( "MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?" );
Log . error ( "If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues" );
2016-04-19 11:25:50 +02:00
});
2020-07-27 20:10:31 +02:00
/**
* The core app.
*
* @class
2016-04-08 22:16:22 +02:00
*/
2020-05-11 22:22:32 +02:00
var App = function () {
2016-04-08 22:16:22 +02:00
var nodeHelpers = [];
2020-07-27 20:10:31 +02:00
/**
* Loads the config file. Combines it with the defaults, and runs the
* callback with the found config as argument.
2016-04-08 22:16:22 +02:00
*
2020-07-27 20:10:31 +02:00
* @param {Function} callback Function to be called after loading the config
2016-04-08 22:16:22 +02:00
*/
2020-05-25 18:57:15 +02:00
var loadConfig = function ( callback ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Loading config ..." );
2016-04-08 22:16:22 +02:00
var defaults = require ( __dirname + "/defaults.js" );
2017-01-24 02:59:20 -03:00
// For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
2017-01-26 18:33:37 -03:00
var configFilename = path . resolve ( global . root_path + "/config/config.js" );
2020-05-11 22:22:32 +02:00
if ( typeof global . configuration_file !== "undefined" ) {
2020-04-20 22:16:23 +02:00
configFilename = path . resolve ( global . configuration_file );
2017-01-24 02:59:20 -03:00
}
2016-04-08 22:16:22 +02:00
try {
fs . accessSync ( configFilename , fs . F_OK );
var c = require ( configFilename );
2016-12-14 18:54:44 +01:00
checkDeprecatedOptions ( c );
2016-04-08 22:16:22 +02:00
var config = Object . assign ( defaults , c );
callback ( config );
} catch ( e ) {
2019-06-05 08:48:22 +02:00
if ( e . code === "ENOENT" ) {
2020-05-11 07:25:42 +02:00
Log . error ( Utils . colors . error ( "WARNING! Could not find config file. Please create one. Starting with default configuration." ));
2016-08-05 19:18:31 +02:00
} else if ( e instanceof ReferenceError || e instanceof SyntaxError ) {
2020-05-11 07:25:42 +02:00
Log . error ( Utils . colors . error ( "WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e . stack ));
2016-08-05 19:18:31 +02:00
} else {
2020-05-11 07:25:42 +02:00
Log . error ( Utils . colors . error ( "WARNING! Could not load config file. Starting with default configuration. Error found: " + e ));
2016-08-05 19:18:31 +02:00
}
2017-03-05 22:05:54 -03:00
callback ( defaults );
2016-04-08 22:16:22 +02:00
}
};
2020-07-27 20:10:31 +02:00
/**
* Checks the config for deprecated options and throws a warning in the logs
* if it encounters one option from the deprecated.js list
*
* @param {object} userConfig The user config
*/
2020-05-11 22:22:32 +02:00
var checkDeprecatedOptions = function ( userConfig ) {
2017-02-21 20:21:46 -03:00
var deprecated = require ( global . root_path + "/js/deprecated.js" );
var deprecatedOptions = deprecated . configs ;
2016-12-14 18:54:44 +01:00
var usedDeprecated = [];
2020-05-11 22:22:32 +02:00
deprecatedOptions . forEach ( function ( option ) {
2016-12-14 18:54:44 +01:00
if ( userConfig . hasOwnProperty ( option )) {
usedDeprecated . push ( option );
}
});
if ( usedDeprecated . length > 0 ) {
2020-05-25 18:57:15 +02:00
Log . warn ( Utils . colors . warn ( "WARNING! Your config is using deprecated options: " + usedDeprecated . join ( ", " ) + ". Check README and CHANGELOG for more up-to-date ways of getting the same functionality." ));
2016-12-14 18:54:44 +01:00
}
2019-06-05 10:23:58 +02:00
};
2016-12-14 18:54:44 +01:00
2020-07-27 20:10:31 +02:00
/**
2016-04-08 22:16:22 +02:00
* Loads a specific module.
*
2020-07-27 20:10:31 +02:00
* @param {string} module The name of the module (including subpath).
* @param {Function} callback Function to be called after loading
2016-04-08 22:16:22 +02:00
*/
2020-05-11 22:22:32 +02:00
var loadModule = function ( module , callback ) {
2016-04-08 22:16:22 +02:00
var elements = module . split ( "/" );
var moduleName = elements [ elements . length - 1 ];
2020-03-15 15:49:34 +01:00
var moduleFolder = __dirname + "/../modules/" + module ;
2016-04-08 22:16:22 +02:00
if ( defaultModules . indexOf ( moduleName ) !== - 1 ) {
2020-03-15 15:49:34 +01:00
moduleFolder = __dirname + "/../modules/default/" + module ;
2016-04-08 22:16:22 +02:00
}
var helperPath = moduleFolder + "/node_helper.js" ;
var loadModule = true ;
try {
fs . accessSync ( helperPath , fs . R_OK );
} catch ( e ) {
loadModule = false ;
2020-05-11 07:25:42 +02:00
Log . log ( "No helper found for module: " + moduleName + "." );
2016-04-08 22:16:22 +02:00
}
if ( loadModule ) {
var Module = require ( helperPath );
var m = new Module ();
2016-10-13 16:42:15 +02:00
if ( m . requiresVersion ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m . requiresVersion + " - Current version: " + global . version );
2016-10-13 16:42:15 +02:00
if ( cmpVersions ( global . version , m . requiresVersion ) >= 0 ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Version is ok!" );
2016-10-13 16:42:15 +02:00
} else {
2020-05-11 07:25:42 +02:00
Log . log ( "Version is incorrect. Skip module: '" + moduleName + "'" );
2016-10-13 16:42:15 +02:00
return ;
}
}
2016-04-08 22:16:22 +02:00
m . setName ( moduleName );
m . setPath ( path . resolve ( moduleFolder ));
nodeHelpers . push ( m );
2016-12-07 17:19:05 +01:00
m . loaded ( callback );
} else {
callback ();
2016-04-08 22:16:22 +02:00
}
};
2020-07-27 20:10:31 +02:00
/**
2016-04-08 22:16:22 +02:00
* Loads all modules.
*
2020-08-01 16:31:42 +02:00
* @param {Module[]} modules All modules to be loaded
2020-07-27 20:10:31 +02:00
* @param {Function} callback Function to be called after loading
2016-04-08 22:16:22 +02:00
*/
2020-05-25 18:57:15 +02:00
var loadModules = function ( modules , callback ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Loading module helpers ..." );
2016-04-08 22:16:22 +02:00
2020-05-11 22:22:32 +02:00
var loadNextModule = function () {
2016-12-07 17:19:05 +01:00
if ( modules . length > 0 ) {
var nextModule = modules [ 0 ];
2020-05-11 22:22:32 +02:00
loadModule ( nextModule , function () {
2016-12-07 17:19:05 +01:00
modules = modules . slice ( 1 );
loadNextModule ();
});
} else {
// All modules are loaded
2020-05-11 07:25:42 +02:00
Log . log ( "All module helpers loaded." );
2016-12-07 17:19:05 +01:00
callback ();
}
};
2016-04-08 22:16:22 +02:00
2016-12-07 17:19:05 +01:00
loadNextModule ();
2016-04-08 22:16:22 +02:00
};
2020-07-27 14:24:30 +02:00
/**
2019-06-04 09:33:53 +02:00
* Compare two semantic version numbers and return the difference.
2016-10-13 16:42:15 +02:00
*
2020-07-27 14:24:30 +02:00
* @param {string} a Version number a.
* @param {string} b Version number b.
2020-07-27 20:10:31 +02:00
*
* @returns {number} A positive number if a is larger than b, a negative
* number if a is smaller and 0 if they are the same
2016-10-13 16:42:15 +02:00
*/
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 ;
}
2020-07-27 20:10:31 +02:00
/**
* Start the core app.
2016-04-08 22:16:22 +02:00
*
2020-07-27 20:10:31 +02:00
* It loads the config, then it loads all modules. When it's done it
* executes the callback with the config as argument.
*
* @param {Function} callback Function to be called after start
2016-04-08 22:16:22 +02:00
*/
2020-05-11 22:22:32 +02:00
this . start = function ( callback ) {
loadConfig ( function ( c ) {
2016-04-08 22:16:22 +02:00
config = c ;
2020-07-04 22:02:39 +02:00
Log . setLogLevel ( config . logLevel );
2016-04-08 22:16:22 +02:00
var modules = [];
for ( var m in config . modules ) {
var module = config . modules [ m ];
2016-11-14 14:43:30 -03:00
if ( modules . indexOf ( module . module ) === - 1 && ! module . disabled ) {
2016-04-08 22:16:22 +02:00
modules . push ( module . module );
}
}
2020-05-25 18:57:15 +02:00
loadModules ( modules , function () {
var server = new Server ( config , function ( app , io ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Server started ..." );
2016-04-08 22:16:22 +02:00
2016-12-07 17:19:05 +01:00
for ( var h in nodeHelpers ) {
var nodeHelper = nodeHelpers [ h ];
nodeHelper . setExpressApp ( app );
nodeHelper . setSocketIO ( io );
nodeHelper . start ();
}
2016-04-08 22:16:22 +02:00
2020-05-11 07:25:42 +02:00
Log . log ( "Sockets connected & modules started ..." );
2016-04-08 22:16:22 +02:00
2016-12-07 17:19:05 +01:00
if ( typeof callback === "function" ) {
callback ( config );
}
});
2016-04-08 22:16:22 +02:00
});
});
};
2017-10-13 16:43:11 -05:00
2020-07-27 20:10:31 +02:00
/**
* Stops the core app. This calls each node_helper's STOP() function, if it
* exists.
*
2017-10-13 16:43:11 -05:00
* Added to fix #1056
*/
2020-05-11 22:22:32 +02:00
this . stop = function () {
2017-10-13 16:43:11 -05:00
for ( var h in nodeHelpers ) {
var nodeHelper = nodeHelpers [ h ];
if ( typeof nodeHelper . stop === "function" ) {
nodeHelper . stop ();
}
}
};
2020-07-27 20:10:31 +02:00
/**
* Listen for SIGINT signal and call stop() function.
2017-10-13 16:43:11 -05:00
*
* Added to fix #1056
* Note: this is only used if running `server-only`. Otherwise
* this.stop() is called by app.on("before-quit"... in `electron.js`
*/
process . on ( "SIGINT" , () => {
2020-05-11 07:25:42 +02:00
Log . log ( "[SIGINT] Received. Shutting down server..." );
2020-05-25 18:57:15 +02:00
setTimeout (() => {
process . exit ( 0 );
}, 3000 ); // Force quit after 3 seconds
2017-10-13 16:43:11 -05:00
this . stop ();
process . exit ( 0 );
});
2019-04-07 16:41:05 +02:00
2020-07-27 20:10:31 +02:00
/**
* Listen to SIGTERM signals so we can stop everything when we
* are asked to stop by the OS.
2019-04-06 20:50:54 +02:00
*/
process . on ( "SIGTERM" , () => {
2020-05-11 07:25:42 +02:00
Log . log ( "[SIGTERM] Received. Shutting down server..." );
2020-05-25 18:57:15 +02:00
setTimeout (() => {
process . exit ( 0 );
}, 3000 ); // Force quit after 3 seconds
2019-04-06 20:50:54 +02:00
this . stop ();
process . exit ( 0 );
});
2016-04-08 22:16:22 +02:00
};
2016-11-14 14:43:30 -03:00
module . exports = new App ();