/*---------------------------------------------------------------------------*/
/* appservice.cpp                                                            */
/* copyright (c) innovaphone 2016                                            */
/*---------------------------------------------------------------------------*/


#include <platform.h>
#include "sdk/common/interface/file.h"
#include "sdk/common/interface/process.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/time.h"
#include "sdk/common/os/iomux.h"
#include "common/ilib/json.h"
#include "../lib/appservice.h"

/*---------------------------------------------------------------------------*/
/* struct AppInstanceArgs                                                    */
/*---------------------------------------------------------------------------*/

AppInstanceArgs::AppInstanceArgs()
{
    memset(this, 0, sizeof(*this));
}

void AppInstanceArgs::Parse(int argc, char ** argv, bool keepDefaults)
{
    if (!keepDefaults) memset(this, 0, sizeof(*this));

    for (int i = 1; i < argc; i++) {
        char * curArg = argv[i];
        char * tok = strchr(curArg, '=');

        if (curArg[0] == '-' && curArg[1] == '-' && tok && tok[1]) {
            curArg += 2;
            tok++;
            size_t nameLen = tok - curArg;

            if (strncmp(curArg, "app-name=", nameLen) == 0) {
                appName = tok;
            }
            else if (strncmp(curArg, "app-domain=", nameLen) == 0) {
                appDomain = tok;
            }
            else if (strncmp(curArg, "app-password=", nameLen) == 0) {
                appPassword = tok;
            }
            else if (strncmp(curArg, "webserver=", nameLen) == 0) {
                webserver = tok;
            }
            else if (strncmp(curArg, "webserver-path=", nameLen) == 0) {
                webserverPath = tok;
            }
            else if (strncmp(curArg, "db-host=", nameLen) == 0) {
                dbHost = tok;
            }
            else if (strncmp(curArg, "db-name=", nameLen) == 0) {
                dbName = tok;
            }
            else if (strncmp(curArg, "db-user=", nameLen) == 0) {
                dbUser = tok;
            }
            else if (strncmp(curArg, "db-password=", nameLen) == 0) {
                dbPassword = tok;
            }
            else if (strncmp(curArg, "log-flags=", nameLen) == 0) {
                logFlags = strtoull(tok, NULL, 0);
            }
            else if (strncmp(curArg, "working-path=", nameLen) == 0) {
                workingPath = tok;
            }
            else {
                //debug->printf("AppInstanceArgs::Parse unknown argument %s", curArg);
            }
        }
    }
}

AppServiceArgs::AppServiceArgs()
{
    memset(this, 0, sizeof(*this));
    this->tlsProfile = ISocketContext::tls_profile_t::TLS_PROFILE_NORMAL;
}

void AppServiceArgs::Parse(int argc, char ** argv, bool keepDefaults)
{
    if (!keepDefaults) memset(this, 0, sizeof(*this));

    for (int i = 1; i < argc; i++) {
        char * curArg = argv[i];
        char * tok;
        size_t nameLen;

        if (curArg[0] == '-' && curArg[1] == '-') {
            curArg += 2;
            tok = strchr(curArg, '=');
            if (tok) {  // arguments with values
                tok++;
                nameLen = tok - curArg;
                if (strncmp(curArg, "manager=", nameLen) == 0) {
                    manager = tok;
                }
                else if (strncmp(curArg, "service-id=", nameLen) == 0) {
                    serviceID = tok;
                }
                else if (strncmp(curArg, "dns=", nameLen) == 0) {
                    appPlatformDNSName = tok;
                }
                else if (strncmp(curArg, "tz=", nameLen) == 0) {
                    appPlatformTz = tok;
                }
                else if (strncmp(curArg, "time-zone=", nameLen) == 0) {
                    appPlatformTimeZone = tok;
                }
                else if (strncmp(curArg, "log-flags=", nameLen) == 0) {
                    logFlags = strtoull(tok, NULL, 0);
                }
                else if (strncmp(curArg, "log-size=", nameLen) == 0) {
                    logFileSize = strtoul(tok, NULL, 10);
                }
                else if (strncmp(curArg, "http=", nameLen) == 0) {
                    webserverPortHttp = (word)strtoul(tok, NULL, 10);
                }
                else if (strncmp(curArg, "https=", nameLen) == 0) {
                    webserverPortHttps = (word)strtoul(tok, NULL, 10);
                }
                else if (strncmp(curArg, "tls-profile=", nameLen) == 0) {
                    tlsProfile = ISocketContext::TlsProfileToEnum(tok);
                }
                else {
                    //debug->printf("AppServiceArgs::Parse unknown argument %s", curArg);
                }
            }
            else {  // boolean arguments 
                if (strcmp(curArg, "segfaulted") == 0) {
                    segfaulted = true;
                }
                else if (strcmp(curArg, "disable-hexdumps") == 0) {
                    disableHexdumps = true;
                }
                else {
                    //debug->printf("AppServiceArgs::Parse unknown argument %s", curArg);
                }
            }
        }
    }
}

/*---------------------------------------------------------------------------*/
/*                        class AppServiceApp                                */
/*---------------------------------------------------------------------------*/

AppServiceApp::AppServiceApp(const char * name, bool websocketOnly)
{
    this->name = _strdup(name);
    this->websocketOnly = websocketOnly;
}

AppServiceApp::~AppServiceApp()
{
    free(this->name);
}

const char * AppServiceApp::GetName()
{
    return this->name;
}

bool AppServiceApp::IsWebsocketOnly()
{
    return this->websocketOnly;
}

/*---------------------------------------------------------------------------*/
/*                        class AppInstancePlugin                            */
/*---------------------------------------------------------------------------*/

AppInstancePlugin::AppInstancePlugin(const char * plugin, const char * icon, const char * langFile, bool wildcard, const char * api)
{
    this->plugin = _strdup(plugin);
    this->icon = _strdup(icon);
    this->langFile = _strdup(langFile);
    this->api = api ? _strdup(api) : NULL;
    this->wildcard = wildcard;
}

AppInstancePlugin::~AppInstancePlugin()
{
    free(this->api);
    free(this->langFile);
    free(this->icon);
    free(this->plugin);
}

const char * AppInstancePlugin::GetPlugin()
{
    return this->plugin;
}

const char * AppInstancePlugin::GetIcon()
{
    return this->icon;
}

const char * AppInstancePlugin::GetLangFile()
{
    return this->langFile;
}

const char * AppInstancePlugin::GetApi()
{
    return this->api;
}

/*---------------------------------------------------------------------------*/
/* class AppInstance                                                         */
/*---------------------------------------------------------------------------*/

AppInstance::AppInstance(class AppService * appService, AppInstanceArgs * args, bool autoStart)
{
    ASSERT(args->appName, "AppInstance::AppInstance appName must be set");
    ASSERT(args->appDomain, "AppInstance::AppInstance appDomain must be set");
    this->logFlags = args->logFlags;
    this->appService = appService;
    this->args.appName = _strdup(args->appName);
    this->args.appDomain = _strdup(args->appDomain);
    this->args.appPassword = args->appPassword ? _strdup(args->appPassword) : NULL;
    this->args.webserver = args->webserver ? _strdup(args->webserver) : NULL;
    this->args.webserverPath = args->webserverPath ? _strdup(args->webserverPath) : NULL;
    this->args.dbHost = args->dbHost ? _strdup(args->dbHost) : NULL;
    this->args.dbName = args->dbName ? _strdup(args->dbName) : NULL;
    this->args.dbUser = args->dbUser ? _strdup(args->dbUser) : NULL;
    this->args.dbPassword = args->dbPassword ? _strdup(args->dbPassword) : NULL;

    this->key = GenerateKey(args->appName, args->appDomain);
    this->started = false;

    if (autoStart) {
        this->Started();
    }
}

AppInstance::~AppInstance()
{
    this->appService->serviceSocket.AppSendStatus(this->args.appName, this->args.appDomain, false, this->args.webserverPath);
    free((void *)args.appName);
    free((void *)args.appDomain);
    free((void *)args.appPassword);
    free((void *)args.webserver);
    free((void *)args.webserverPath);
    free((void *)args.dbHost);
    free((void *)args.dbName);
    free((void *)args.dbUser);
    free((void *)args.dbPassword);
    free(key);
}

void AppInstance::Started()
{
    if (!this->started) {
        this->appService->serviceSocket.AppSendStatus(this->args.appName, this->args.appDomain, true, this->args.webserverPath);
        this->started = true;
    }
}

char * AppInstance::GenerateKey(const char * appName, const char * appDomain)
{
    char * key = (char *)malloc(strlen(appName) + strlen(appDomain) + 2);
    *key = '\0';
    strcat(key, appName);
    strcat(key, "@");
    strcat(key, appDomain);
    return key;
}

int AppInstance::btree_compare(void * key)
{
    return strcmp(this->key, (char *)key);
}

int AppInstance::btree_compare(class btree * b)
{
    return this->btree_compare(static_cast<AppInstance *>(b)->key);
}

const char * AppInstance::GetAppName() const
{
    return this->args.appName;
}

const char * AppInstance::GetAppDomain() const
{
    return this->args.appDomain;
}

const char * AppInstance::GetKey() const
{
    return this->key;
}

void AppInstance::SetAlarm(word code, ServiceAlarmSeverity severity, const char * text, const char * details)
{
    this->appService->serviceSocket.AppSendAlarm(ServiceAlarmType::ALARM_TYPE_SET, severity, code, text, details, appService->GetAppServiceId(), this->args.appName, this->args.appDomain);
}
void AppInstance::ClearAlarm(word code, ServiceAlarmSeverity severity, const char * text, const char * details)
{
    this->appService->serviceSocket.AppSendAlarm(ServiceAlarmType::ALARM_TYPE_CLEAR, severity, code, text, details, appService->GetAppServiceId(), this->args.appName, this->args.appDomain);
}
void AppInstance::SendEvent(word code, ServiceAlarmSeverity severity, const char * text, const char * details)
{
    this->appService->serviceSocket.AppSendAlarm(ServiceAlarmType::ALARM_TYPE_ERROR, severity, code, text, details, appService->GetAppServiceId(), this->args.appName, this->args.appDomain);
}

/*---------------------------------------------------------------------------*/
/* class AppService                                                          */
/*---------------------------------------------------------------------------*/

AppService::AppService(class IIoMux * const iomux, class ISocketProvider * const localSocketProvider, AppServiceArgs * args)
    : serviceSocket(iomux, localSocketProvider, this)
{
    this->appsClosed = false;
    this->closing = false;
    this->appInstances = NULL;
    this->cert = NULL;
    this->certLen = 0;
    this->appPlatformDNSName = args->appPlatformDNSName ? _strdup(args->appPlatformDNSName) : NULL;
    this->appPlatformTz = args->appPlatformTz ? _strdup(args->appPlatformTz) : NULL;
    this->appPlatformTimeZone = args->appPlatformTimeZone ? _strdup(args->appPlatformTimeZone) : NULL;
    this->serviceID = args->serviceID ? _strdup(args->serviceID) : NULL;
    this->logFlags = args->logFlags;
    this->webserverPortHttp = (args->webserverPortHttp > 0 ? args->webserverPortHttp : 80);
    this->webserverPortHttps = (args->webserverPortHttps > 0 ? args->webserverPortHttps : 443);
    this->tlsProfile = args->tlsProfile;

    if (args->manager != NULL) {
        serviceSocket.Connect(args->manager, args->serviceID);
    }
    if (args->disableHexdumps) {
        debug->SetHexdump(false);
    }
    if (args->logFileSize > 0) {
        debug->SetLogFileSize(args->logFileSize);
    }
    serviceSocket.SetLogFlags(this->logFlags);
    debug->printf("AppService::AppService %s%s", _BUILD_STRING_, args->segfaulted ? " (restart after a crash)" : "");
}

AppService::~AppService()
{
    ASSERT(!this->appInstances, "AppService::~AppService appInstances not NULL");
    //debug->printf("AppService::~AppService");
    free(this->cert);
    free(this->appPlatformDNSName);
    free(this->appPlatformTz);
    free(this->appPlatformTimeZone);
    free(this->serviceID);
}

void AppService::UpdateServerCertificate(const char * cert)
{
    free(this->cert);
    this->certLen = 0;
    if (cert && *cert) {
        this->cert = (byte *)_strdup(cert);
        this->certLen = strlen(cert);
    }
    //debug->printf("AppService::UpdateServerCertificate cert:%x certLen:%u", this->cert, this->certLen);
    for (class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find_left(); appInstance; appInstance = (class AppInstance *)appInstances->btree_find_next_left(appInstance->GetKey())) {
        appInstance->ServerCertificateUpdate(this->cert, this->certLen);
    }
}

void AppService::AppStart(AppInstanceArgs * args)
{
    debug->printf("AppService::AppStart %s@%s", args->appName, args->appDomain);
    AppInstance * instance = this->CreateInstance(args);
    appInstances = appInstances->btree_put(instance);
    instance->ServerCertificateUpdate(this->cert, this->certLen);
}

void AppService::AppStopped(class AppInstance * instance)
{
    debug->printf("AppService::AppStopped %s@%s", instance->GetAppName(), instance->GetAppDomain());
    appInstances = appInstances->btree_get(instance);
    delete instance;
    if (this->closing) {    // only call TryStop if we are really closing completely
        TryStop();
    }
}

void AppService::AppStop(const char * appName, const char * appDomain)
{
    char * key = AppInstance::GenerateKey(appName, appDomain);
    class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find(key);
    free(key);
    ASSERT(appInstance, "AppService::AppStop appInstance not found");
    debug->printf("AppService::AppStop %s@%s", appName, appDomain);
    appInstance->Stop();
}

void AppService::AppSetLogFlags(const char * appName, const char * appDomain, ulong64 logFlags)
{
    if (appName && appDomain) {
        char * key = AppInstance::GenerateKey(appName, appDomain);
        class AppInstance * instance = (class AppInstance *)appInstances->btree_find(key);
        free(key);
        ASSERT(instance, "AppService::AppSetLogFlags instance not found");
        instance->SetLogFlags(logFlags);
    }
    else {
        this->logFlags = logFlags;
    }
}

void AppService::UpdateAppPlatformDNSName(const char * dnsName)
{
    free(this->appPlatformDNSName);
    this->appPlatformDNSName = dnsName ? _strdup(dnsName) : NULL;
}

void AppService::UpdateAppPlatformTimeZone(const char * tz)
{
    free(this->appPlatformTz);
    this->appPlatformTz = tz ? _strdup(tz) : NULL;
}

void AppService::UpdateAppPlatformTimeZoneIana(const char * timeZone)
{
    free(this->appPlatformTimeZone);
    this->appPlatformTimeZone = timeZone ? _strdup(timeZone) : NULL;
    //debug->printf("AppService::UpdateAppPlatformTimeZoneIana %s", timeZone);
    for (class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find_left(); appInstance; appInstance = (class AppInstance *)appInstances->btree_find_next_left(appInstance->GetKey())) {
        appInstance->AppPlatformTimeZoneUpdate(this->appPlatformTimeZone);
    }
}

void AppService::AppDomain(const char * appName, const char * appDomain, const char * domain, const char * pwd)
{
    if (appName && appDomain) {
        char * key = AppInstance::GenerateKey(appName, appDomain);
        class AppInstance * instance = (class AppInstance *)appInstances->btree_find(key);
        free(key);
        ASSERT(instance, "AppService::AppDomain instance not found");
        instance->SetAppDomain(domain, pwd);
    }
}

void AppService::UpdateAppPassword(const char * appName, const char * appDomain, const char * appPassword)
{
    if (appName && appDomain) {
        char * key = AppInstance::GenerateKey(appName, appDomain);
        class AppInstance * instance = (class AppInstance *)appInstances->btree_find(key);
        free(key);
        if (instance) {
            free((void *)instance->GetArgs()->appPassword);
            instance->GetArgs()->appPassword = _strdup(appPassword);
            instance->AppPasswordUpdate(appPassword);
        }
    }
}

void AppService::UpdateTlsProfile(ISocketContext::tls_profile_t tlsProfile)
{
    this->tlsProfile = tlsProfile;
    for (class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find_left(); appInstance; appInstance = (class AppInstance *)appInstances->btree_find_next_left(appInstance->GetKey())) {
        appInstance->TlsProfileUpdate(this->tlsProfile);
    }
}

void AppService::CloseApps()
{
    //debug->printf("AppService::CloseApps");
    this->appsClosed = true;
    class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find_left();
    class AppInstance * nextInstance;
    for (; appInstance; appInstance = nextInstance) {
        nextInstance = (class AppInstance *)appInstances->btree_find_next_left(appInstance->GetKey());
        appInstance->Stop();
    }
}

void AppService::SetAllInstancesLogFlags(ulong64 logFlags)
{
    //debug->printf("AppService::SetAllInstancesLogFlags %llu", logFlags);
    class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find_left();
    for (; appInstance; appInstance = (class AppInstance *)appInstances->btree_find_next_left(appInstance->GetKey())) {
        appInstance->SetLogFlags(logFlags);
    }
    SetLogFlags(logFlags);
}

void AppService::TryStop()
{
    if (!this->appsClosed) {
        this->CloseApps();
    }
    if (appInstances) {
        return;	// this may leave us already ready to terminate
    }
    serviceSocket.AppServiceShutdownComplete();
}

void AppService::AppServiceShutdown()
{
    debug->printf("AppService::Shutdown");
    this->closing = true;
    TryStop();
}

void AppService::AppServiceShutdownTimeout()
{
    debug->printf("AppService::ShutdownTimeout %s %p", this->serviceID, appInstances);
    class AppInstance * appInstance = (class AppInstance *)appInstances->btree_find_left();
    for (; appInstance; appInstance = (class AppInstance *)appInstances->btree_find_next_left(appInstance->GetKey())) {
        appInstance->AppInstanceShutdownTimeout();
    }
}

const char * AppService::GetAppServiceId() const
{
    return this->serviceID;
}

const char * AppService::GetAppPlatformDNSName() const
{
    return this->appPlatformDNSName;
}

const char * AppService::GetAppPlatformTimeZoneString() const
{
    return this->appPlatformTz;
}

const char * AppService::GetAppPlatformTimeZone() const
{
    return this->appPlatformTimeZone;
}

/*---------------------------------------------------------------------------*/
/* class AppServiceSocket                                                    */
/*---------------------------------------------------------------------------*/
AppServiceSocket::AppServiceSocket(class IIoMux * const iomux, class ISocketProvider * const localSocketProvider, UAppServiceSocket * const user) :
iomux(iomux),
localSocketProvider(localSocketProvider),
user(user)
{
    iomux->RegisterShutdownHandler(this);
    this->connected = false;
    this->appServiceClosed = false;
    this->closing = false;
    this->recvBuffer = NULL;
    this->serviceID = NULL;
    this->socket = NULL;
}

AppServiceSocket::~AppServiceSocket()
{
    if (this->serviceID) free(this->serviceID);
}

void AppServiceSocket::Connect(const char * address, const char * serviceID)
{
    this->socket = localSocketProvider->CreateSocket(iomux, this, this, false);
    this->serviceID = _strdup(serviceID);
    socket->Connect(address);
}

void AppServiceSocket::SocketConnectComplete(ISocket * const socket)
{
    this->connected = true;

    char sb[8192] = "\0";
    class json_io send(sb);
    word w = send.add_object(JSON_ID_ROOT, 0);
    send.add_string(w, "mt", "AppServiceInitConnection");
    send.add_string(w, "appServiceID", this->serviceID);
    word v = send.add_object(w, "version");
    const char * version = 0;
    const char * build = 0;
    const char * label = 0;
    user->AppServiceVersion(version, build, label);
    send.add_string(v, "id", version ? version : _VERSION_STR_);
    send.add_string(v, "build", build ? build : _BUILD_STRING_);
    send.add_string(v, "label", label ? label : _RELEASE_STATE_);
    word a = send.add_array(w, "apps");
    istd::list<AppServiceApp> appList;
    user->AppServiceApps(&appList);
    class AppServiceApp * app = NULL;
    for (app = appList.front(); app; app = app->goNext()) {
        word obj = send.add_object(a, NULL);
        send.add_string(obj, "name", app->GetName());
        if (app->IsWebsocketOnly()) {
            send.add_bool(obj, "wsOnly", true);
        }
    }
    AppSocketMessageSend(send, sb);
    while ((app = appList.front()) != NULL) {
        delete app;
    }
    //debug->printf("AppServiceSocket::SocketConnectComplete");
    SocketRecv(sizeof(dword), true);
}

void AppServiceSocket::AppSocketMessageSend(class json_io & send, char * sb)
{
    word len = send.encode();

    byte sendLen[4];
    WRITE_DWORD_NET(sendLen, len);
    this->socket->Send(sendLen, 4);
    this->socket->Send(sb, len);
}

void AppServiceSocket::SocketRecv(size_t len, bool partial)
{
    if (this->recvBuffer) free(this->recvBuffer);
    this->recvBuffer = (char *)malloc(len + 1);
    socket->Recv(this->recvBuffer, len, partial);
}

void AppServiceSocket::AppSendStatus(const char * appName, const char * appDomain, bool status, const char * webserverPath)
{
    if (connected) {
        char * ws = (char *)alloca(strlen(webserverPath) + 1);
        _sprintf(ws, "%s", webserverPath);
        if (*ws == '/')
            ws++;
        char sb[6144] = "\0";
        class json_io send(sb);
        word w = send.add_object(JSON_ID_ROOT, 0);
        send.add_string(w, "mt", "AppStatusUpdate");
        send.add_string(w, "appName", appName);
        send.add_string(w, "appDomain", appDomain);
        send.add_bool(w, "status", status);
        word a = send.add_array(w, "controls");
        istd::list<AppInstancePlugin> pluginList;
        user->AppInstancePlugins(&pluginList);
        class AppInstancePlugin * plugin = NULL;
        for (plugin = pluginList.front(); plugin; plugin = plugin->goNext()) {
            char * uri = (char *)alloca(1024);
            char * png = (char *)alloca(1024);
            char * lang = (char *)alloca(1024);
            _snprintf(uri, 1024, "%s/%s", ws, plugin->GetPlugin());
            _snprintf(png, 1024, "%s/%s", ws, plugin->GetIcon());
            _snprintf(lang, 1024, "%s/%s", ws, plugin->GetLangFile());
            word obj = send.add_object(a, NULL);
            send.add_string(obj, "uri", uri);
            send.add_string(obj, "png", png);
            send.add_string(obj, "lang", lang);
            send.add_string(obj, "api", plugin->GetApi());
            send.add_bool(obj, "wildcard", plugin->wildcard);
        }
        AppSocketMessageSend(send, sb);
        while ((plugin = pluginList.front()) != NULL) {
            delete plugin;
        }
    }
    else
        ;//debug->printf("AppServiceSocket::AppSendStatus status:%d", status);
}

void AppServiceSocket::AppSocketShutdown()
{
    //debug->printf("AppServiceSocket::AppSocketShutdown");
    this->connected = false;
    this->socket->Shutdown();
}

void AppServiceSocket::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    //debug->printf("AppServiceSocket::SocketShutdown reason:%i", reason);
    delete socket;
    this->socket = NULL;
    this->connected = false;
    this->iomux->Shutdown();
}

void AppServiceSocket::SocketSendResult(ISocket * const socket)
{
}

void AppServiceSocket::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    if (len == sizeof(dword)) {
        size_t length = READ_DWORD_NET(buf);
        SocketRecv(length, false);
        return;
    }

    class json_io recv((char *)buf);
    ((char *)buf)[len] = 0;

    if (recv.decode()) {
        word root_last = 0, t;
        word w = recv.get_object(JSON_ID_ROOT, root_last);
        const char * mt = recv.get_string(w, "mt");
        //debug->printf("AppServiceSocket::SocketRecvResult mt:%s", mt);

        if (!(strcmp(mt, "AppServiceStop"))) {
            this->iomux->Shutdown();
            free(buf);
            this->recvBuffer = NULL;
            return;
        }
        else if (!(strcmp(mt, "SetServiceLogging"))) {
            ulong64 logFlags = recv.get_ulong64(w, "logFlags");
            bool present = false;
            bool disableHexdumps = recv.get_bool(w, "disableHexdumps", &present);
            if (present) {
                debug->SetHexdump(!disableHexdumps);
            }
            dword logSize = recv.get_unsigned(w, "logSize", &present);
            if (present && logSize > 0) {
                debug->SetLogFileSize((off_t)logSize);
            }
            this->logFlags = logFlags;
            user->AppSetLogFlags(NULL, NULL, logFlags);
        }
        else if (!(strcmp(mt, "SetInstanceLogFlags"))) {
            const char * appName = recv.get_string(w, "appName");
            const char * appDomain = recv.get_string(w, "appDomain");
            ulong64 logFlags = recv.get_ulong64(w, "logFlags");
            user->AppSetLogFlags(appName, appDomain, logFlags);
        }
        else if (!strcmp(mt, "SetCertificate")) {
            user->UpdateServerCertificate(recv.get_string(w, "cert"));
        }
        else if (!strcmp(mt, "LogFilesDeleted")) {
            debug->ReOpenLogFile();
        }
        else if (!strcmp(mt, "AppPlatformDns")) {
            user->UpdateAppPlatformDNSName(recv.get_string(w, "dns"));
        }
        else if (!strcmp(mt, "AppPlatformTz")) {
            user->UpdateAppPlatformTimeZone(recv.get_string(w, "tz"));
            user->UpdateAppPlatformTimeZoneIana(recv.get_string(w, "timeZone"));
        }
        else if (!strcmp(mt, "AppDomain")) {
            user->AppDomain(recv.get_string(w, "appName"), recv.get_string(w, "appDomain"), recv.get_string(w, "domain"), recv.get_string(w, "pwd"));
        }
        else if (!strcmp(mt, "AppStart") || !strcmp(mt, "AppStop")) {
            t = recv.get_object(w, "instance");
            if (t != JSON_ID_NONE) {
                AppInstanceArgs args;
                args.appName = recv.get_string(t, "appName");
                args.appDomain = recv.get_string(t, "appDomain");
                args.appPassword = recv.get_string(t, "appPassword");
                args.webserver = recv.get_string(t, "webserver");
                args.webserverPath = recv.get_string(t, "webserverPath");
                args.dbHost = recv.get_string(t, "dbHost");
                args.dbName = recv.get_string(t, "dbName");
                args.dbUser = recv.get_string(t, "dbUser");
                args.dbPassword = recv.get_string(t, "dbPassword");
                args.logFlags = recv.get_ulong64(t, "logFlags");
                args.workingPath = recv.get_string(t, "workingPath");

                if (!strcmp(mt, "AppStart")) {
                    user->AppStart(&args);
                }
                else if (!strcmp(mt, "AppStop")) {
                    user->AppStop(args.appName, args.appDomain);
                }
            }
        }
        else if (!(strcmp(mt, "SetLogging"))) {
            debug->SetLogging(recv.get_bool(w, "enable"));
        }
        else if (!strcmp(mt, "SetAppPassword")) {
            const char * appDomain = recv.get_string(w, "appDomain");
            const char * appName = recv.get_string(w, "appName");
            const char * appPassword = recv.get_string(w, "appPassword");
            user->UpdateAppPassword(appName, appDomain, appPassword);
        }
        else if (!strcmp(mt, "SetTlsProfile")) {
            const char * tlsProfile = recv.get_string(w, "tlsProfile");
            user->UpdateTlsProfile(ISocketContext::TlsProfileToEnum(tlsProfile));
        }
    }
    SocketRecv(sizeof(dword), true);
}

void AppServiceSocket::SocketRecvCanceled(ISocket * const socket, void * buf)
{
    free(buf);
    this->recvBuffer = NULL;
}

void AppServiceSocket::AppServiceShutdownComplete()
{
    this->appServiceClosed = true;
    if (closing)
        TryShutdown();
}

void AppServiceSocket::Shutdown()
{
    this->closing = true;
    TryShutdown();
}

void AppServiceSocket::ShutdownTimeout()
{
    debug->printf("AppServiceSocket::ShutdownTimeout %s %s %s %d %d %d",
        this->serviceID, this->GetAppName(), this->GetAppDomain(), !appServiceClosed, this->connected, this->socket);
    if (!appServiceClosed) {
        user->AppServiceShutdownTimeout();
    }
}

void AppServiceSocket::TryShutdown()
{
    if (!appServiceClosed) {
        user->AppServiceShutdown();
        return;
    }
    if (this->connected) {
        this->connected = false;
        char sb[200] = "\0";
        class json_io send(sb);
        word w = send.add_object(JSON_ID_ROOT, 0);
        send.add_string(w, "mt", "AppServiceCloseComplete");
        send.add_string(w, "appServiceID", this->serviceID);
        AppSocketMessageSend(send, sb);
        return;						// this is an async op in any case, so return
    }
    else if (this->socket)
        return;
    iomux->ShutdownComplete(this);
}

void AppServiceSocket::AppSendAlarm(ServiceAlarmType type, ServiceAlarmSeverity severity, word code, const char * text, const char * details, const char * serviceID, const char * appName, const char * appDomain)
{
    if (this->socket) {
        char sb[65000] = "\0";
        char temp[2048];
        char * tmp = temp;
        class json_io send(sb);
        word w = send.add_object(JSON_ID_ROOT, 0);
        send.add_string(w, "mt", "AppAlarm");
        send.add_string(w, "serviceID", serviceID);
        if (appName && appDomain) {
            send.add_string(w, "appName", appName);
            send.add_string(w, "appDomain", appDomain);
        }
        send.add_unsigned(w, "type", type, tmp);
        send.add_unsigned(w, "severity", severity, tmp);
        send.add_unsigned(w, "code", code, tmp);
        send.add_ulong64(w, "time", ITime::TimeStampMilliseconds() / 1000, tmp);
        send.add_string(w, "text", text);
        send.add_string(w, "details", details);
        AppSocketMessageSend(send, sb);
    }
}
