Common

This documentation gives instructions how to develop App Services in C++, that can run on the innovaphone App Platform. The header files for interfaces and libraries provided by the SDK can be found in the subfolders of sdk/common.

Folder Structure

sdk/common/interface
sdk/common/lib
sdk/common/ilib
sdk/common/os
sdk/common/service
sdk/common/linux

Developing App Services

main.cpp

For each application a main function is needed that initializes the application and all of its dependencies. This main function should be located in an applicationname-main.cpp file in the root folder of the project.

Includes

The main file includes all needed header files in the following order:

Initialization

In the main() function all dependencies of the application are initialized using the corresponding Create() function. Then the application is started. All dependencies are passed to the constructor of the application using pointers to their interface class (dependency injection).

As the main file is not shared across platforms it may use system calls, if needed.

Code Example

The following code snippet shows the example-main.cpp of an application called Example that is dependent of ''IoMux'', ''WebserverPluginProvider'', ''PostgreSQLDatabaseProvider'' and a ''TCPSocketProvider''.

#include "platform/platform.h
#include <string.h>
#include <sys/un.h>
#include <unistd.h>
#include "common/os/iomux.h"
#include "common/interface/webserver_plugin.h"
#include "common/interface/database.h"
#include "common/interface/socket.h"
#include "common/interface/httpfile.h"
#include "common/lib/appservice.h"
#include "common/lib/appwebsocket.h"
#include "example/example.h"
int main(void)
{
    // dependencies
    class IIoMux * iomux = IIoMux::Create();
    IWebserverPluginProvider * webserverPluginProvider = CreateWebserverPluginProvider();
    IDatabaseProvider * databaseProvider = CreatePostgreSQLDatabaseProvider();
    ISocketProvider * socketProvider = CreateTCPSocketProvider();
    // command line arguments
    AppServiceArgs  serviceArgs;
    serviceArgs.Parse(argc, argv);
   
    // start application
    class Example * service = new Example(iomux, webserverPluginProvider, databaseProvider, socketProvider, &serviceArgs);
    iomux->Run();
   
    // clean-up dependencies
    delete service;
    delete socketProvider;
    delete databaseProvider;
    delete webserverPluginProvider
    delete iomux;
    delete debug;
    // return from application
    return 0;
}
    

App Service and App Instance

Each app should have a main class appservicename (App Service class) which is derived from the base class AppService (found in appservice.h). This base class deals with the comunication with the App Platform Manager by establishing a local unix domain socket. Creation of new instances, stopping and starting them and enabling of log flags options are the core of such information exchange. This class also defines and saves the main provided apps and provided plugins (PBX Manager plugins, if applicable)

    class ExampleService : public AppService {
        //dependencies
        class IIoMux * iomux;
        class ISocketProvider * localSocketProvider;
        class ISocketProvider * tcpSocketProvider;
        class ISocketProvider * tlsSocketProvider;
        class IWebserverPluginProvider * webserverPluginProvider;
        class IDatabaseProvider * databaseProvider;

    public:
        ExampleService (class IIoMux * const iomux, class ISocketProvider * localSocketProvider, 
                        class ISocketProvider * tcpSocketProvider, class ISocketProvider * tlsSocketProvider, 
                        IWebserverPluginProvider * const webserverPluginProvider, class IDatabaseProvider * databaseProvider, AppServiceArgs * args);
        ~ ExampleService ();
        //creates the app instance class
        class AppInstance * CreateInstance(AppInstanceArgs * args) override;
        // creates and saves the provided apps
        void AppServiceApps(istd::list * appList) override;
        //creates and saves the app plugins
        void AppInstancePlugins(istd::list * pluginList) override;
    };
    

The ExampleService::CreateInstance(AppInstanceArgs * args) function is then used to create a new app instance with a main class that is derived from the class AppInstance (found in appservice.h).

        class AppInstance * ExampleService::CreateInstance(AppInstanceArgs * args) {
            return new Example(iomux, localSocketProvider, tcpSocketProvider, tlsSocketProvider, webserverPluginProvider, databaseProvider, args);
        }
    

The new app instance class listens to incoming WebSocket messages. Therefore, WebSocket functions along with database related methods should be implemented in this class.

        class Example: public UWebserverPlugin, public UDatabase, public AppInstance {
            //dependencies
            class IIoMux * iomux;
            class ISocketProvider * tcpSocketProvider;
            class ISocketProvider * tlsSocketProvider;
            class IWebserverPlugin * webserverPlugin;
        public:
            Example (IIoMux * const iomux, ISocketProvider * localSocketProvider, class ISocketProvider * tcpSocketProvider, 
                     class ISocketProvider * tlsSocketProvider, IWebserverPluginProvider * const webserverPluginProvider, 
                     IDatabaseProvider * databaseProvider, struct AppInstanceArgs * args);
            ~ Example ();

            void Stop() override;
            //websocket and database methods
            void WebserverPluginWebsocketListenResult(IWebserverPlugin * plugin, const char * path, const char * registeredPathForRequest, const char * host) override;
            void DatabaseConnectComplete(IDatabase * const database) override;
            void DatabaseShutdown(IDatabase * const database, db_error_t reason) override;
            void DatabaseError(IDatabase * const database, db_error_t error) override;
        };
    

When an app is stopped, the function Example::Stop() is called. This function should be implemented in the main class and should close all WebSocket, database or HTTP connections. When all the connections are terminated, a call to AppService::AppStopped(class AppInstance * instance) is needed indicating that the app has stopped completely.

Including HTTP static files

The App hosted by the App Service is a web application that consists of a number of static files. That files need to be included in the binary of the App Service. In order to achieve that you need to

Example:

$(OUTDIR)/obj/apps.cpp: apidemo/apps/*.*
    $(IP_SRC)/exe/httpfiles -k -d apidemo/apps -t $(OUTDIR) -o $(OUTDIR)/obj/apps.cpp \
    apidemo.htm,0,HTTP_GZIP \
    apidemo.png,0,HTTP_GZIP \
    innovaphone.apidemo.js,0,HTTP_GZIP
        

Coding Conventions

Naming

Classes and function names use UpperCamelCase.

Variables and parameters use lowerCamelCase.

Braces

In general opening braces are put on the same line. Closing braces are put on a line on their own.

if (x > 3) {
    ...
}
else {
    ...
}
        

However, there is one special case, namely functions: they have the opening brace at the beginning of the next line.

int MyFunction(int x)
{
    ...
}
        

White space

Indentations are 4 spaces. Tabs are not to be used. There is no common definition what a tab is, if looking at code with an editor with a different understanding of tabs, tabs in the code can make code almost unreadable.

Additional spaces do not necessarily make code more readable. It is better to use spaces the same way as in common written language or in mathematical expressions.

But
int MyFunction(int a, int b, int c)
{
    ...
}
 
if (a == b || a == c) {
    ...
}
 
for (int i = 0; i < 1000; i++) {
    ...
}
        

To seperate functional blocks in the code a single empty line is good enough.

if/else

if (x == y) {
    ...
}
else if (x > y) {
    ...
}
else {
    ....
}
    

switch/case

switch (c) {
case 1:
    ...
    break;
case 2:
    {
        int x;
        ...
    }
    break;
default:
    ...
}
    

do/while

do {
    ...
}
while (x < y)
    

for

for (int i = 0; i < 1000; i++) {
    ...
}
    

classes

class x : public y {
    ...
public:
    ...
};
    

Interfaces

All system interfaces are designed in a way that there are abstract classes, with a name starting with 'I' which provide assynchronous services for applications. These classes are typcally allocated with a 'Create' member function. In case of the main class of an interface this is a static Create function.

The interfaces also define abstract 'User' classes, with a name starting with 'U'. The application uses these as base classes to own classes. These classes define callback functions which are called by the interface to signal events like command completion or incoming requests.

Interfaces must be asynchronous. That means that calls to an 'I' class return immediately. If the function call triggers a lengthy action, the application is notified when the action is finished using a callback to the corresponding 'U' class. Calls to functions in an 'I' class should not trigger callbacks to the 'U' class, before the interface call returns. All interfaces in common/interface are guaranteed to follow that behaviour.

Example:

class ITask {
public:
    virtual void Start(class UTask * user) = 0;
};
 
class UTask {
public:
    virtual void TaskComplete(class ITask * const task) = 0;
    virtual void TaskFailed(class ITask * const task) = 0;
};