root/src/servers/registrar/Registrar.cpp
/*
 * Copyright 2001-2015, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Ingo Weinhold, ingo_weinhold@gmx.de
 */

#include "Registrar.h"

#include <stdio.h>
#include <string.h>

#include <exception>

#include <Application.h>
#include <Clipboard.h>
#include <Message.h>
#include <MessengerPrivate.h>
#include <OS.h>
#include <RegistrarDefs.h>
#include <RosterPrivate.h>
#include <system_info.h>

#include "AuthenticationManager.h"
#include "ClipboardHandler.h"
#include "Debug.h"
#include "EventQueue.h"
#include "MessageDeliverer.h"
#include "MessageEvent.h"
#include "MessageRunnerManager.h"
#include "MessagingService.h"
#include "MIMEManager.h"
#include "PackageWatchingManager.h"
#include "ShutdownProcess.h"
#include "TRoster.h"


/*!
        \class Registrar
        \brief The application class of the registrar.

        Glues the registrar services together and dispatches the roster messages.
*/

using std::nothrow;
using namespace BPrivate;

//! Name of the event queue.
static const char *kEventQueueName = "timer_thread";


/*!     \brief Creates the registrar application class.
        \param error Passed to the BServer constructor for returning an
                   error code.
*/
Registrar::Registrar(status_t* _error)
        :
        BServer(B_REGISTRAR_SIGNATURE, B_REGISTRAR_PORT_NAME, -1, false, _error),
        fRoster(NULL),
        fClipboardHandler(NULL),
        fMIMEManager(NULL),
        fEventQueue(NULL),
        fMessageRunnerManager(NULL),
        fShutdownProcess(NULL),
        fAuthenticationManager(NULL),
        fPackageWatchingManager(NULL)
{
        FUNCTION_START();

        set_thread_priority(find_thread(NULL), B_NORMAL_PRIORITY + 1);
}


/*!     \brief Frees all resources associated with the registrar.

        All registrar services, that haven't been shut down earlier, are
        terminated.
*/
Registrar::~Registrar()
{
        FUNCTION_START();
        Lock();
        fEventQueue->Die();
        delete fAuthenticationManager;
        delete fPackageWatchingManager;
        delete fMessageRunnerManager;
        delete fEventQueue;
        fMIMEManager->Lock();
        fMIMEManager->Quit();
        RemoveHandler(fClipboardHandler);
        delete fClipboardHandler;
        delete fRoster;
        // Invalidate the global be_roster, so that the BApplication destructor
        // won't dead-lock when sending a message to itself.
        BRoster::Private().SetTo(BMessenger(), BMessenger());
        FUNCTION_END();
}


/*!     \brief Overrides the super class version to dispatch roster specific
                   messages.
        \param message The message to be handled
*/
void
Registrar::MessageReceived(BMessage *message)
{
        try {
                _MessageReceived(message);
        } catch (std::exception& exception) {
                char buffer[1024];
                snprintf(buffer, sizeof(buffer),
                        "Registrar::MessageReceived() caught exception: %s",
                        exception.what());
                debugger(buffer);
        } catch (...) {
                debugger("Registrar::MessageReceived() caught unknown exception");
        }
}


/*!     \brief Overrides the super class version to initialize the registrar
                   services.
*/
void
Registrar::ReadyToRun()
{
        FUNCTION_START();

        // create message deliverer
        status_t error = MessageDeliverer::CreateDefault();
        if (error != B_OK) {
                FATAL("Registrar::ReadyToRun(): Failed to create the message "
                        "deliverer: %s\n", strerror(error));
        }

        // create event queue
        fEventQueue = new EventQueue(kEventQueueName);

        // create authentication manager
        fAuthenticationManager = new AuthenticationManager;
        fAuthenticationManager->Init();

        // create roster
        fRoster = new TRoster;
        fRoster->Init();

        // create clipboard handler
        fClipboardHandler = new ClipboardHandler;
        AddHandler(fClipboardHandler);

        // create MIME manager
        fMIMEManager = new MIMEManager;
        fMIMEManager->Run();

        // create message runner manager
        fMessageRunnerManager = new MessageRunnerManager(fEventQueue);

        // init the global be_roster
        BRoster::Private().SetTo(be_app_messenger, BMessenger(NULL, fMIMEManager));

        // create the messaging service
        error = MessagingService::CreateDefault();
        if (error != B_OK) {
                ERROR("Registrar::ReadyToRun(): Failed to init messaging service "
                        "(that's by design when running under R5): %s\n", strerror(error));
        }

        // create the package watching manager
        fPackageWatchingManager = new PackageWatchingManager;

        // Sanity check roster after team deletion
        BMessenger target(this);
        BMessenger::Private messengerPrivate(target);

        port_id port = messengerPrivate.Port();
        int32 token = messengerPrivate.Token();
        __start_watching_system(-1, B_WATCH_SYSTEM_TEAM_DELETION, port, token);
        fRoster->CheckSanity();
                // Clean up any teams that exited before we started watching

        FUNCTION_END();
}


/*!     \brief Overrides the super class version to avoid termination of the
                   registrar until the system shutdown.
*/
bool
Registrar::QuitRequested()
{
        FUNCTION_START();
        // The final registrar must not quit. At least not that easily. ;-)
        return BApplication::QuitRequested();
}


/*!     \brief Returns the registrar's event queue.
        \return The registrar's event queue.
*/
EventQueue*
Registrar::GetEventQueue() const
{
        return fEventQueue;
}


/*!     \brief Returns the Registrar application object.
        \return The Registrar application object.
*/
Registrar*
Registrar::App()
{
        return dynamic_cast<Registrar*>(be_app);
}


void
Registrar::_MessageReceived(BMessage *message)
{
        switch (message->what) {
                // general requests
                case B_REG_GET_MIME_MESSENGER:
                {
                        PRINT("B_REG_GET_MIME_MESSENGER\n");
                        BMessenger messenger(NULL, fMIMEManager);
                        BMessage reply(B_REG_SUCCESS);
                        reply.AddMessenger("messenger", messenger);
                        message->SendReply(&reply);
                        break;
                }

                case B_REG_GET_CLIPBOARD_MESSENGER:
                {
                        PRINT("B_REG_GET_CLIPBOARD_MESSENGER\n");
                        BMessenger messenger(fClipboardHandler);
                        BMessage reply(B_REG_SUCCESS);
                        reply.AddMessenger("messenger", messenger);
                        message->SendReply(&reply);
                        break;
                }

                // shutdown process
                case B_REG_SHUT_DOWN:
                {
                        PRINT("B_REG_SHUT_DOWN\n");

                        _HandleShutDown(message);
                        break;
                }
                case B_REG_IS_SHUT_DOWN_IN_PROGRESS:
                {
                        PRINT("B_REG_IS_SHUT_DOWN_IN_PROGRESS\n");

                        _HandleIsShutDownInProgress(message);
                        break;
                }
                case B_REG_TEAM_DEBUGGER_ALERT:
                {
                        if (fShutdownProcess != NULL)
                                fShutdownProcess->PostMessage(message);
                        break;
                }

                // roster requests
                case B_REG_ADD_APP:
                        fRoster->HandleAddApplication(message);
                        break;
                case B_REG_COMPLETE_REGISTRATION:
                        fRoster->HandleCompleteRegistration(message);
                        break;
                case B_REG_IS_APP_REGISTERED:
                        fRoster->HandleIsAppRegistered(message);
                        break;
                case B_REG_REMOVE_PRE_REGISTERED_APP:
                        fRoster->HandleRemovePreRegApp(message);
                        break;
                case B_REG_REMOVE_APP:
                        fRoster->HandleRemoveApp(message);
                        break;
                case B_REG_SET_THREAD_AND_TEAM:
                        fRoster->HandleSetThreadAndTeam(message);
                        break;
                case B_REG_SET_SIGNATURE:
                        fRoster->HandleSetSignature(message);
                        break;
                case B_REG_GET_APP_INFO:
                        fRoster->HandleGetAppInfo(message);
                        break;
                case B_REG_GET_APP_LIST:
                        fRoster->HandleGetAppList(message);
                        break;
                case B_REG_UPDATE_ACTIVE_APP:
                        fRoster->HandleUpdateActiveApp(message);
                        break;
                case B_REG_BROADCAST:
                        fRoster->HandleBroadcast(message);
                        break;
                case B_REG_START_WATCHING:
                        fRoster->HandleStartWatching(message);
                        break;
                case B_REG_STOP_WATCHING:
                        fRoster->HandleStopWatching(message);
                        break;
                case B_REG_GET_RECENT_DOCUMENTS:
                        fRoster->HandleGetRecentDocuments(message);
                        break;
                case B_REG_GET_RECENT_FOLDERS:
                        fRoster->HandleGetRecentFolders(message);
                        break;
                case B_REG_GET_RECENT_APPS:
                        fRoster->HandleGetRecentApps(message);
                        break;
                case B_REG_ADD_TO_RECENT_DOCUMENTS:
                        fRoster->HandleAddToRecentDocuments(message);
                        break;
                case B_REG_ADD_TO_RECENT_FOLDERS:
                        fRoster->HandleAddToRecentFolders(message);
                        break;
                case B_REG_ADD_TO_RECENT_APPS:
                        fRoster->HandleAddToRecentApps(message);
                        break;
                case B_REG_CLEAR_RECENT_DOCUMENTS:
                        fRoster->ClearRecentDocuments();
                        break;
                case B_REG_CLEAR_RECENT_FOLDERS:
                        fRoster->ClearRecentFolders();
                        break;
                case B_REG_CLEAR_RECENT_APPS:
                        fRoster->ClearRecentApps();
                        break;
                case B_REG_LOAD_RECENT_LISTS:
                        fRoster->HandleLoadRecentLists(message);
                        break;
                case B_REG_SAVE_RECENT_LISTS:
                        fRoster->HandleSaveRecentLists(message);
                        break;

                // message runner requests
                case B_REG_REGISTER_MESSAGE_RUNNER:
                        fMessageRunnerManager->HandleRegisterRunner(message);
                        break;
                case B_REG_UNREGISTER_MESSAGE_RUNNER:
                        fMessageRunnerManager->HandleUnregisterRunner(message);
                        break;
                case B_REG_SET_MESSAGE_RUNNER_PARAMS:
                        fMessageRunnerManager->HandleSetRunnerParams(message);
                        break;
                case B_REG_GET_MESSAGE_RUNNER_INFO:
                        fMessageRunnerManager->HandleGetRunnerInfo(message);
                        break;

                // package watching requests
                case B_REG_PACKAGE_START_WATCHING:
                case B_REG_PACKAGE_STOP_WATCHING:
                        fPackageWatchingManager->HandleStartStopWatching(message);
                        break;
                case B_PACKAGE_UPDATE:
                        fPackageWatchingManager->NotifyWatchers(message);
                        break;

                // internal messages
                case B_SYSTEM_OBJECT_UPDATE:
                {
                        team_id team = (team_id)message->GetInt32("team", -1);
                        if (team >= 0 && message->GetInt32("opcode", 0) == B_TEAM_DELETED)
                                fRoster->HandleRemoveApp(message);
                        break;
                }
                case B_REG_SHUTDOWN_FINISHED:
                        if (fShutdownProcess) {
                                fShutdownProcess->PostMessage(B_QUIT_REQUESTED,
                                        fShutdownProcess);
                                fShutdownProcess = NULL;
                        }
                        break;

                case kMsgAppServerStarted:
                {
                        fRoster->HandleAppServerStarted(message);

                        // Don't pass this message on to our BApplication, as that may deadlock.
                        break;
                }

                default:
                        BApplication::MessageReceived(message);
                        break;
        }
}


/*!     \brief Handle a shut down request message.
        \param request The request to be handled.
*/
void
Registrar::_HandleShutDown(BMessage *request)
{
        status_t error = B_OK;

        // check, whether we're already shutting down
        if (fShutdownProcess)
                error = B_SHUTTING_DOWN;

        bool needsReply = true;
        if (error == B_OK) {
                // create a ShutdownProcess
                fShutdownProcess = new(nothrow) ShutdownProcess(fRoster, fEventQueue);
                if (fShutdownProcess) {
                        error = fShutdownProcess->Init(request);
                        if (error == B_OK) {
                                DetachCurrentMessage();
                                fShutdownProcess->Run();
                                needsReply = false;
                        } else {
                                delete fShutdownProcess;
                                fShutdownProcess = NULL;
                        }
                } else
                        error = B_NO_MEMORY;
        }

        if (needsReply)
                ShutdownProcess::SendReply(request, error);
}


/*!     \brief Handle a is shut down in progress request message.
        \param request The request to be handled.
*/
void
Registrar::_HandleIsShutDownInProgress(BMessage *request)
{
        BMessage reply(B_REG_SUCCESS);
        reply.AddBool("in-progress", fShutdownProcess != NULL);
        request->SendReply(&reply);
}


//      #pragma mark -


/*!     \brief Creates and runs the registrar application.

        The main thread is renamed.

        \return 0.
*/
int
main()
{
        FUNCTION_START();

        // Create the global be_clipboard manually -- it will not work, since it
        // wants to talk to the registrar in its constructor, but it doesn't have
        // to and we would otherwise deadlock when initializing our GUI in the
        // app thread.
        be_clipboard = new BClipboard(NULL);

        // create and run the registrar application
        status_t error;
        Registrar *app = new Registrar(&error);
        if (error != B_OK) {
                fprintf(stderr, "REG: Failed to create the BApplication: %s\n",
                        strerror(error));
                return 1;
        }

        // rename the main thread
        rename_thread(find_thread(NULL), "roster");

        PRINT("app->Run()...\n");

        try {
                app->Run();
        } catch (std::exception& exception) {
                char buffer[1024];
                snprintf(buffer, sizeof(buffer),
                        "registrar main() caught exception: %s", exception.what());
                debugger(buffer);
        } catch (...) {
                debugger("registrar main() caught unknown exception");
        }

        PRINT("delete app...\n");
        delete app;

        FUNCTION_END();
        return 0;
}