diff --git a/Doxyfile b/Doxyfile
index c22c59df..0fb84b18 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -672,7 +672,7 @@ FILE_VERSION_FILTER =
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
# tag is left empty.
-LAYOUT_FILE =
+LAYOUT_FILE = DoxygenLayout.xml
# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
# the reference definitions. This must be a list of .bib files. The .bib
diff --git a/DoxygenLayout.xml b/DoxygenLayout.xml
new file mode 100644
index 00000000..de524405
--- /dev/null
+++ b/DoxygenLayout.xml
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp
index d36bf76b..f0b0fb88 100644
--- a/src/controller/controller.cpp
+++ b/src/controller/controller.cpp
@@ -1,6 +1,31 @@
+/// \page api API calls
+/// The controller listens for commands through a JSON-based API. This page describes the API in full.
+///
+/// A default interface implementing this API as a single HTML page is included in the controller itself. This default interface will be send for invalid API requests, and is thus triggered by default when a browser attempts to access the API port directly.
+/// The default API port is 4242 - but this can be changed through both the API and commandline parameters.
+///
+/// To send an API request, simply send a HTTP request to this port for any file, and include either a GET or POST parameter called `"command"`, containing a JSON object as payload. Nearly all members of the request object are optional, and described below.
+/// A simple example request logging in to the system would look like this:
+///
+/// GET /api?command={"authorize":{"username":"test","password":"941d7b88b2312d4373aff526cf7b6114"}} HTTP/1.0
+///
+/// Or, when properly URL encoded:
+///
+/// GET /api?command=%7B%22authorize%22%3A%7B%22username%22%3A%22test%22%2C%22password%22%3A%22941d7b88b2312d4373aff526cf7b6114%22%7D%7D HTTP/1.0
+///
+/// The server is quite lenient about not URL encoding your strings, but it's a good idea to always do it, anyway.
+/// See the `"authorize"` section below for more information about security and logging in.
+///
+/// As mentioned above, sending an invalid request will trigger a response containing the default interface. As you may not want to receive a big HTML page as response to an invalid request, requesting the file `"/api"` (as done in the example above) will force a JSON response, even when the request is invalid.
+///
+/// You may also include a `"callback"` or `"jsonp"` HTTP variable, to trigger JSONP compatibility mode. JSONP is useful for getting around the cross-domain scripting protection in most modern browsers. Developers creating non-JavaScript applications will most likely not want to use JSONP mode, though nothing is stopping you if you really want to.
+///
+/// \brief Listing of all controller API calls.
+
/// \file controller.cpp
/// Contains all code for the controller executable.
+
#include
//#include
#include
@@ -66,6 +91,45 @@ namespace Controller {
///\param Request The request to be parsed.
///\param Response The location to store the generated response.
///\param conn The user to be checked for authorization.
+ ///
+ /// \api
+ /// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections, you are required to re-sent authentication with every API request made. To prevent plaintext sending of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
+ /// If the user is not authorized, this request is the only request the server will respond to until properly authorized.
+ /// `"authorize"` requests take the form of:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// //username to login as
+ /// "username": "test",
+ /// //hash of password to login with. Send empty value when no challenge for the hash is known yet.
+ /// //When the challenge is known, the value to be used here can be calculated as follows:
+ /// // MD5( MD5("secret") + challenge)
+ /// //Where "secret" is the plaintext password.
+ /// "password": ""
+ /// }
+ /// ~~~~~~~~~~~~~~~
+ /// and are responded to as:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// //current login status. Either "OK", "CHALL", "NOACC" or "ACC_MADE".
+ /// "status": "CHALL",
+ /// //Random value to be used in hashing the password.
+ /// "challenge": "abcdef1234567890"
+ /// }
+ /// ~~~~~~~~~~~~~~~
+ /// The challenge string is sent for all statuses, except `"NOACC"`, where it is left out.
+ /// A status of `"OK"` means you are currently logged in and have access to all other API requests.
+ /// A status of `"CHALL"` means you are not logged in, and a challenge has been provided to login with.
+ /// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// //username to create, as plain text
+ /// "new_username": "test",
+ /// //password to set, as plain text
+ /// "new_password": "secret"
+ /// }
+ /// ~~~~~~~~~~~~~~~
+ /// Please note that this is NOT secure. At all. Never use this mechanism over a public network!
+ /// A status of `"ACC_MADE"` indicates the account was created successfully and can now be used to login as normal.
void Authorize(JSON::Value & Request, JSON::Value & Response, ConnectedUser & conn){
time_t Time = time(0);
tm * TimeInfo = localtime( &Time);
@@ -113,6 +177,47 @@ namespace Controller {
///\brief Check the submitted configuration and handle things accordingly.
///\param in The new configuration.
///\param out The location to store the resulting configuration.
+ ///
+ /// \api
+ /// `"config"` requests take the form of:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// "controller": { //controller settings
+ /// "interface": null, //interface to listen on. Defaults to all interfaces.
+ /// "port": 4242, //port to listen on. Defaults to 4242.
+ /// "username": null //username to drop privileges to. Defaults to root.
+ /// },
+ /// "protocols": [ //enabled connectors / protocols
+ /// {
+ /// "connector": "HTTP" //Name of the connector to enable
+ /// //any required and/or optional settings may be given here as "name": "value" pairs inside this object.
+ /// },
+ /// //above structure repeated for all enabled connectors / protocols
+ /// ],
+ /// "serverid": "", //human-readable server identifier, optional.
+ /// }
+ /// ~~~~~~~~~~~~~~~
+ /// and are responded to as:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// "controller": { //controller settings
+ /// "interface": null, //interface to listen on. Defaults to all interfaces.
+ /// "port": 4242, //port to listen on. Defaults to 4242.
+ /// "username": null //username to drop privileges to. Defaults to root.
+ /// },
+ /// "protocols": [ //enabled connectors / protocols
+ /// {
+ /// "connector": "HTTP" //Name of the connector to enable
+ /// //any required and/or optional settings may be given here as "name": "value" pairs inside this object.
+ /// "online": 1 //boolean value indicating if the executable is running or not
+ /// },
+ /// //above structure repeated for all enabled connectors / protocols
+ /// ],
+ /// "serverid": "", //human-readable server identifier, as configured.
+ /// "time": 1398982430, //current unix time
+ /// "version": "2.0.2/8.0.1-23-gfeb9322/Generic_64" //currently running server version string
+ /// }
+ /// ~~~~~~~~~~~~~~~
void CheckConfig(JSON::Value & in, JSON::Value & out){
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
if (jit->first == "version" || jit->first == "time"){
@@ -536,6 +641,14 @@ int main(int argc, char ** argv){
Response["conversion"]["status"] = myConverter.getStatus();
}
}
+ ///
+ /// \api
+ /// `"save"` requests are always empty:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {}
+ /// ~~~~~~~~~~~~~~~
+ /// Sending this request will cause the controller to write out its currently active configuration to the configuration file it was loaded from (the default being `./config.json`).
+ ///
if (Request.isMember("save")){
if( Controller::WriteFile(Controller::conf.getString("configFile"), Controller::Storage.toString())){
Controller::Log("CONF", "Config written to file on request through API");
@@ -554,6 +667,21 @@ int main(int argc, char ** argv){
Response["config"]["serverid"] = "";
}
//sent any available logs and statistics
+ ///
+ /// \api
+ /// `"log"` responses are always sent, and cannot be requested:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// [
+ /// [
+ /// 1398978357, //unix timestamp of this log message
+ /// "CONF", //shortcode indicating the type of log message
+ /// "Starting connector: {\"connector\":\"HTTP\"}" //string containing the log message itself
+ /// ],
+ /// //the above structure repeated for all logs
+ /// ]
+ /// ~~~~~~~~~~~~~~~
+ /// It's possible to clear the stored logs by sending an empty `"clearstatlogs"` request.
+ ///
Response["log"] = Controller::Storage["log"];
//clear log and statistics if requested
if (Request.isMember("clearstatlogs")){
diff --git a/src/controller/controller_capabilities.cpp b/src/controller/controller_capabilities.cpp
index 9d661666..884bf5b3 100644
--- a/src/controller/controller_capabilities.cpp
+++ b/src/controller/controller_capabilities.cpp
@@ -51,6 +51,74 @@ namespace Controller {
///\brief Checks the capabilities of the system.
///\param capa The location to store the capabilities.
+ ///
+ /// \api
+ /// `"capabilities"` requests are always empty:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {}
+ /// ~~~~~~~~~~~~~~~
+ /// and are responded to as:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// "connectors": { // a list of installed connectors
+ /// "FLV": { //name of the connector. This is based on the executable filename, with the "MistIn" / "MistConn" prefix stripped.
+ /// "codecs": [ //supported combinations of codecs.
+ /// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously available channels and the codec options for those channels
+ /// ],
+ /// "deps": "HTTP", //dependencies on other connectors, if any.
+ /// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of this connector
+ /// "methods": [ //list of supported request methods
+ /// {
+ /// "handler": "http", //what handler to use for this request method. The "http://" part of a URL, without the "://".
+ /// "priority": 5, // priority of this request method, higher is better.
+ /// "type": "flash/7" //type of request method - usually name of plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
+ /// }
+ /// ],
+ /// "name": "HTTP_Progressive_FLV", //Full name of this connector.
+ /// "optional": { //optional parameters
+ /// "username": { //name of the parameter
+ /// "help": "Username to drop privileges to - default if unprovided means do not drop privileges", //human-readable help text
+ /// "name": "Username", //human-readable name of parameter
+ /// "option": "--username", //command-line option to use
+ /// "type": "str" //type of option - "str" or "num"
+ /// }
+ /// //above structure repeated for all (optional) parameters
+ /// },
+ /// //above structure repeated, as "required" for required parameters, if any.
+ /// "socket": "http_progressive_flv", //unix socket this connector listens on, if any
+ /// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name and may not be the first or last character.
+ /// "url_prefix": "/progressive/$/", //URL prefix to match, if any. The $ substitutes the stream name and may not be the first or last character.
+ /// "url_rel": "/$.flv" //relative URL where to access a stream through this connector.
+ /// }
+ /// //... above structure repeated for all installed connectors.
+ /// },
+ /// "cpu": [ //a list of installed CPUs
+ /// {
+ /// "cores": 4, //amount of cores for this CPU
+ /// "mhz": 1645, //speed in MHz for this CPU
+ /// "model": "Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz", //model identifier, for humans
+ /// "threads": 8 //amount of simultaneously executing threads that are supported on this CPU
+ /// }
+ /// //above structure repeated for all installed CPUs
+ /// ],
+ /// "load": {
+ /// "fifteen": 72,
+ /// "five": 81,
+ /// "memory": 42,
+ /// "one": 124
+ /// },
+ /// "mem": {
+ /// "cached": 1989, //current memory usage of system caches, in MiB
+ /// "free": 2539, //free memory, in MiB
+ /// "swapfree": 0, //free swap space, in MiB
+ /// "swaptotal": 0, //total swap space, in MiB
+ /// "total": 7898, //total memory, in MiB
+ /// "used": 3370 //used memory, in MiB (excluding system caches, listed separately)
+ /// },
+ /// "speed": 6580, //total speed in MHz of all CPUs cores summed together
+ /// "threads": 8 //total count of all threads of all CPUs summed together
+ /// }
+ /// ~~~~~~~~~~~~~~~
void checkCapable(JSON::Value & capa){
//capa.null();
capa.removeMember("cpu");
diff --git a/src/controller/controller_statistics.cpp b/src/controller/controller_statistics.cpp
index 23848904..02a5c38a 100644
--- a/src/controller/controller_statistics.cpp
+++ b/src/controller/controller_statistics.cpp
@@ -91,7 +91,7 @@ bool Controller::hasViewers(std::string streamName){
/// This takes a "clients" request, and fills in the response data.
///
/// \api
-/// `"client"` requests take the form of:
+/// `"clients"` requests take the form of:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// //array of streamnames to accumulate. Empty means all.
diff --git a/src/controller/controller_streams.cpp b/src/controller/controller_streams.cpp
index d0c4c8f0..5bec9167 100644
--- a/src/controller/controller_streams.cpp
+++ b/src/controller/controller_streams.cpp
@@ -305,6 +305,52 @@ namespace Controller {
///\brief Parse a given stream configuration.
///\param in The requested configuration.
///\param out The new configuration after parsing.
+ ///
+ /// \api
+ /// `"streams"` requests take the form of:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// "streamname_here": { //name of the stream
+ /// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP or hostname of the machine allowed to push live data. Empty means everyone is allowed to push live data.
+ /// "DVR": 30000 //optional. For live streams, indicates the requested minimum size of the available DVR buffer in milliseconds.
+ /// },
+ /// //the above structure repeated for all configured streams
+ /// }
+ /// ~~~~~~~~~~~~~~~
+ /// and are responded to as:
+ /// ~~~~~~~~~~~~~~~{.js}
+ /// {
+ /// "streamname_here": { //name of the configured stream
+ /// "error": "Available", //error state, if any. "Available" is a special value for VoD streams, indicating it has no current viewers (is not active), but is available for activation.
+ /// "h_meta": 1398113185, //unix time the stream header (if any) was last processed for metadata
+ /// "l_meta": 1398115447, //unix time the stream itself was last processed for metadata
+ /// "meta": { //available metadata for this stream, if any
+ /// "format": "dtsc", //detected media source format
+ /// "tracks": { //list of tracks in this stream
+ /// "audio_AAC_2ch_48000hz_2": {//human-readable track name
+ /// "bps": 16043,
+ /// "channels": 2,
+ /// "codec": "AAC",
+ /// "firstms": 0,
+ /// "init": "\u0011Vå\u0000",
+ /// "lastms": 596480,
+ /// "rate": 48000,
+ /// "size": 16,
+ /// "trackid": 2,
+ /// "type": "audio"
+ /// },
+ /// //the above structure repeated for all tracks
+ /// },
+ /// "vod": 1 //indicates VoD stream, or "live" to indicated live stream.
+ /// },
+ /// "name": "a", //the stream name, guaranteed to be equal to the object name.
+ /// "online": 2, //online state. 0 = error, 1 = active, 2 = inactive.
+ /// "source": "/home/thulinma/a.dtsc" //source for this stream, as configured.
+ /// },
+ /// //the above structure repeated for all configured streams
+ /// }
+ /// ~~~~~~~~~~~~~~~
+ /// Through this request, ALL streams must always be configured. To remove a stream, simply leave it out of the request. To add a stream, simply add it to the request. To edit a stream, simply edit it in the request. The LTS edition has additional requests that allow per-stream changing of the configuration.
void CheckStreams(JSON::Value & in, JSON::Value & out){
//check for new streams and updates
AddStreams(in, out);