Source: application.js

monohm.provide ("sensible.Application");

/**
 * Application which advertises via MDNS and serves via HTTP
 *
 * @constructor
 * @param {function} inCallback - function to call on completion
 */

sensible.Application = function (inCallback)
{
	console.log ("sensible.Application()");
	
	// do this first, so it's valid throughout the startup process
	gSensibleApplication = this;
	
	var	self = this;
	
	this.loadConfig
	(
		function (inError)
		{
			if (inError)
			{
				console.log ("error loading config...");
				console.log (inError);
				
				// careful here, Ajax will call your error handler
				// if your success handler throws
				if (inCallback)
				{
					try
					{
						inCallback.call (self, inError);
					}
					catch (inError)
					{
						if (inError)
						{
							inCallback.call (self, inError);
						}
					}
				}
			}
			else
			{
				self.loadProperties
				(
					function (inError)
					{
						if (inError)
						{
							console.log ("error loading properties...");
							console.log (inError);
						}
						else
						{
							self.onBeforeStart
							(
								function ()
								{
									try
									{
										self.start
										(
											function (inError)
											{
												if (inError)
												{
													inCallback.call (self, inError);
												}
												else
												{
													self.onAfterStart
													(
														function ()
														{
															inCallback.call (self);
														}
													);
												}
											}
										);
									}
									catch (inError)
									{
										if (inError)
										{
											inCallback.call (self, inError);
										}
									}
								}
							);
						}
					}
				);
			}
		}
	);
}

/**
 * Return the value of a specific property
 *
 * @param {string} inName - name of property to return
 */
 
sensible.Application.prototype.getProperty = function (inName)
{
	var	value = null;
	
	var	property = this.propertiesByKey [inName];
	
	if (property)
	{
		value = property.value;
	}
	else
	{
		console.error ("sensible.Application.getProperty() can't find property for name " + inName);
	}
}

/**
 * Set the value of a specific property
 *
 * @param {string} inName - name of property whose value to set
 * @param {object} inValue - value
 */
 
sensible.Application.prototype.setProperty = function (inName, inValue)
{
	var	property = this.propertiesByKey [inName];
	
	if (property)
	{
		property.value = inValue;
	}
	else
	{
		console.error ("sensible.Application.setProperty() can't find property for name " + inName);
	}
}

// DEFAULT APPLICATION IMPLEMENTATION

/**
 * Start everything - MDNS advertisement and HTTP server
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.start = function (inCallback)
{
	var	startWebServer = true;
	
	if (typeof this.config.startWebServer == "boolean")
	{
		startWebServer = this.config.startWebServer;
	}
	
	if (startWebServer)
	{
		this.startWebServer ();
	}
	else
	{
		console.log ("config says not to start the web server");
	}
	
	var	advertiseMDNS = true;
	
	if (typeof this.config.advertiseMDNS == "boolean")
	{
		advertiseMDNS = this.config.advertiseMDNS;
	}
	
	if (advertiseMDNS)
	{
		// we virtualise this because now, not every app type uses our MDNS implementation
		this.startMDNS (inCallback);
	}
	else
	{
		console.log ("config says not to advertise with mDNS");
		inCallback.call (this);
	}
}			


// APPLICATION INTERFACE

/**
 * Do app-specific tasks immediately prior to running start()
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.onBeforeStart = function (inCallback)
{
	console.log ("Application.onBeforeStart()");
	inCallback ();
}

/**
 * Do app-specific tasks immediately after running start()
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.onAfterStart = function (inCallback)
{
	console.log ("Application.onAfterStart()");
	inCallback ();
}

// PLATFORM INTERFACE

/**
 * Load configuration - by default from sensible-config.json
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.loadConfig = function (inCallback)
{
	throw new Error ("sensible.Application.loadConfig() called - abstract");
}

/**
 * Load properties - by default from sensible-properties.json
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.loadProperties = function (inCallback)
{
	throw new Error ("sensible.Application.loadProperties() called - abstract");
}

/**
 * Register the hostname with MDNS - name comes from config
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.registerHost = function (inCallback)
{
	throw new Error ("sensible.Application.registerHost() called - abstract")
}

/**
 * Register the service with MDNS - service info comes from config
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.registerService = function (inCallback)
{
	throw new Error ("sensible.Application.registerService() called - abstract")
}

/**
 * Save configuration - by default to sensible-config.json
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.saveConfig = function ()
{
	throw new Error ("sensible.Application.saveConfig() called - abstract")
}

/**
 * Save properties - by default to sensible-properties.json
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.saveProperties = function ()
{
	throw new Error ("sensible.Application.saveProperties() called - abstract")
}

/**
 * Start MDNS
 * Overridden by Application subclasses if they don't use our mDNS implementation.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.startMDNS = function (inCallback)
{
	this.mdns = new sensible.MDNS ();

	var	self = this;

	this.mdns.start
	(
		function (inError)
		{
			if (inError)
			{
				inCallback.call (self, inError);
			}
			else
			{
				self.registerService
				(
					function ()
					{
						self.registerHost
						(
							function ()
							{
								inCallback.call (self);
							}
						);
					}
				);
			}
		}
	);
}

/**
 * Start HTTP server
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.startWebServer = function (inCallback)
{
	throw new Error ("sensible.Application.startWebServer() called - abstract")
}

/**
 * Stop everything - MDNS advertisement and HTTP server
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.stop = function ()
{
	throw new Error ("sensible.Application.stop() called - abstract")
}

/**
 * Stop HTTP server
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */
 
sensible.Application.prototype.stopHTTPServer = function ()
{
	throw new Error ("sensible.Application.stopHTTPServer() called - abstract")
}

/**
 * Unregister the hostname with MDNS - name comes from config
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.unregisterHost = function (inCallback)
{
	throw new Error ("sensible.Application.unregisterHost() called - abstract")
}

/**
 * Unregister the service with MDNS - service info comes from config
 * Usually overridden by platform-specific Application class.
 *
 * @param {function} inCallback - function to be called on completion
 */

sensible.Application.prototype.unregisterService = function ()
{
	throw new Error ("sensible.Application.unregisterService() called - abstract")
}

// PRIVATE

// CONTROLLER ACTIONS

sensible.Application.prototype.config_get = function (inRequest, inCallback)
{
	var	response = new Object ();
	response.type = "json";
	response.object = this.config;
	
	var	name = inRequest.parameters.name;
	
	if (name && name.length)
	{
		response.object = this.config [name];

		if (! response.object)
		{
			response.type = "error";
			response.error = "config key " + name + " not found";
		}
	}
	
	inCallback (response);
}

sensible.Application.prototype.config_set = function (inRequest, inCallback)
{
	var	changed = false;
	
	for (var key in inRequest.parameters)
	{
		if (key == "name")
		{
			if (newValue && (newValue != this.config.name))
			{
				this.config.name = newValue;
				changed = true;
			}
		}
		else
		if (key == "type")
		{
			if (newValue && (newValue != this.config.type))
			{
				this.config.type = newValue;
				changed = true;
			}
		}
		else
		if (key == "port")
		{
			var	newPort = parseInt (newValue);
			
			if (isNaN (newPort))
			{
				console.log ("bad numeric value for setting port : " + inRequest.parameters [key]);
			}
			else
			{
				if (this.config.port != newPort)
				{
					this.config.port = newPort;
					changed = true;
				}
			}
		}
		else
		{
			console.log ("config_set with unknown key " + key);
		}
	}
	
	if (changed)
	{
		this.saveConfig ();
		this.stop ();
		this.start ();
	}
	
	var	response = new Object ();
	response.type = "json";
	response.object = this.config;
	
	inCallback (response);
}

sensible.Application.prototype.properties_get = function (inRequest, inCallback)
{
	var	response = new Object ();
	response.type = "json";
	response.object = this.properties;
	
	var	name = inRequest.parameters.name;
	
	if (name && name.length)
	{
		response.object = this.propertiesByKey [name];

		if (! response.object)
		{
			response.type = "error";
			response.error = "property " + name + " not found";
		}
	}
	
	inCallback (response);
}

sensible.Application.prototype.properties_set = function (inRequest, inCallback)
{
	var	changed = false;
	
	for (var key in inRequest.parameters)
	{
		// sometimes we get zero length keys, from URLs with trailing &s
		// a little bug in someone's URL parser perchance
		if (key && key.length)
		{
			var	newValue = inRequest.parameters [key];
			var	property = this.propertiesByKey [key];
			
			if (property)
			{
				var	oldValueType = typeof (property.value);
				
				if (oldValueType == "string")
				{
					if (property.value != newValue)
					{
						changed = true;
						property.value = newValue;
					}
				}
				else
				if (oldValueType == "number")
				{
					var	numberValue = parseInt (newValue);
					
					if (isNaN (numberValue))
					{
						console.log ("bad numeric value for setting " + key + ": " + newValue);
					}
					else
					{
						if (numberValue < property.minimum || numberValue > property.maximum)
						{
							console.log ("value out of range for setting " + key + ": " + numberValue);
						}
						else
						{
							if (property.value != numberValue)
							{
								changed = true;
								property.value = numberValue;
							}
						}
					}
				}
				else
				if (oldValueType == "boolean")
				{
					var	booleanValue = (newValue.toLowerCase () == "true");
					
					if (property.value != booleanValue)
					{
						changed = true;
						property.value = booleanValue;
					}
				}
				else
				{
					console.log ("unknown type for setting " + key + ": " + oldValueType);
				}
			}
			else
			{
				console.log ("properties_set with unknown key " + key);
			}
		}
	}
	
	if (changed)
	{
		this.saveProperties ();
	}
	
	var	response = new Object ();
	response.type = "json";
	response.object = this.properties;
	
	inCallback (response);
}

// STATIC DATA

sensible.Application.kFileExtensionToContentType = 
{
	"gif" : "image/gif",
	"html" : "text/html",
	"ico" : "image/x-icon",
	"jpg" : "image/jpeg",
	"png" : "image/png",
	"txt" : "text/plain"
};