root/src/servers/media_addon/MediaAddonServer.cpp
/*
 * Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2013 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

/*
 * Copyright (c) 2002-2004, Marcus Overhagen <marcus@overhagen.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <map>
#include <stdio.h>
#include <vector>

#include <Alert.h>
#include <Application.h>
#include <Beep.h>
#include <Directory.h>
#include <Entry.h>
#include <MediaAddOn.h>
#include <MediaRoster.h>
#include <MessageRunner.h>
#include <Path.h>
#include <Roster.h>
#include <String.h>

#include <AddOnMonitorHandler.h>
#include <DataExchange.h>
#include <DormantNodeManager.h>
#include <MediaDebug.h>
#include <MediaMisc.h>
#include <MediaRosterEx.h>
#include <MediaSounds.h>
#include <Notifications.h>
#include <ServerInterface.h>

#include "MediaFilePlayer.h"
#include "SystemTimeSource.h"


//#define USER_ADDON_PATH "../add-ons/media"


typedef std::vector<media_node> NodeVector;


struct AddOnInfo {
                        media_addon_id          id;
                        bool                            wants_autostart;
                        int32                           flavor_count;

                        NodeVector                      active_flavors;

                        // if != NULL, need to call gDormantNodeManager->PutAddOn(id)
                        BMediaAddOn*            addon;
};


class MediaAddonServer : BApplication {
public:
                                                                MediaAddonServer(const char* signature);
        virtual                                         ~MediaAddonServer();
        virtual void                            ReadyToRun();
        virtual bool                            QuitRequested();
        virtual void                            MessageReceived(BMessage* message);

private:
        class MonitorHandler;
        friend class MonitorHandler;

                        void                            _AddOnAdded(const char* path, ino_t fileNode);
                        void                            _AddOnRemoved(ino_t fileNode);
                        void                            _HandleMessage(int32 code, const void* data,
                                                                        size_t size);

                        void                            _PutAddonIfPossible(AddOnInfo& info);
                        void                            _InstantiatePhysicalInputsAndOutputs(
                                                                        AddOnInfo& info);
                        void                            _InstantiateAutostartFlavors(AddOnInfo& info);
                        void                            _DestroyInstantiatedFlavors(AddOnInfo& info);

                        void                            _ScanAddOnFlavors(BMediaAddOn* addOn);

                        port_id                         _ControlPort() const { return fControlPort; }

        static  status_t                        _ControlThread(void* arg);

private:
        typedef std::map<ino_t, media_addon_id> FileMap;
        typedef std::map<media_addon_id, AddOnInfo> InfoMap;

                        FileMap                         fFileMap;
                        InfoMap                         fInfoMap;

                        BMediaRoster*           fMediaRoster;
                        MonitorHandler*         fMonitorHandler;
                        BMessageRunner*         fPulseRunner;
                        port_id                         fControlPort;
                        thread_id                       fControlThread;
                        bool                            fStartup;
                        bool                            fStartupSound;
                        SystemTimeSource*       fSystemTimeSource;
};


class MediaAddonServer::MonitorHandler : public AddOnMonitorHandler {
public:
                                                                MonitorHandler(MediaAddonServer* server);

        virtual void                            AddOnEnabled(const add_on_entry_info* info);
        virtual void                            AddOnDisabled(const add_on_entry_info* info);

private:
                        MediaAddonServer*       fServer;
};


#if DEBUG >= 2
static void
DumpFlavorInfo(const flavor_info* info)
{
        printf("  name = %s\n", info->name);
        printf("  info = %s\n", info->info);
        printf("  internal_id = %" B_PRId32 "\n", info->internal_id);
        printf("  possible_count = %" B_PRId32 "\n", info->possible_count);
        printf("  flavor_flags = 0x%" B_PRIx32, info->flavor_flags);
        if (info->flavor_flags & B_FLAVOR_IS_GLOBAL) printf(" B_FLAVOR_IS_GLOBAL");
        if (info->flavor_flags & B_FLAVOR_IS_LOCAL) printf(" B_FLAVOR_IS_LOCAL");
        printf("\n");
        printf("  kinds = 0x%" B_PRIx64, info->kinds);
        if (info->kinds & B_BUFFER_PRODUCER) printf(" B_BUFFER_PRODUCER");
        if (info->kinds & B_BUFFER_CONSUMER) printf(" B_BUFFER_CONSUMER");
        if (info->kinds & B_TIME_SOURCE) printf(" B_TIME_SOURCE");
        if (info->kinds & B_CONTROLLABLE) printf(" B_CONTROLLABLE");
        if (info->kinds & B_FILE_INTERFACE) printf(" B_FILE_INTERFACE");
        if (info->kinds & B_ENTITY_INTERFACE) printf(" B_ENTITY_INTERFACE");
        if (info->kinds & B_PHYSICAL_INPUT) printf(" B_PHYSICAL_INPUT");
        if (info->kinds & B_PHYSICAL_OUTPUT) printf(" B_PHYSICAL_OUTPUT");
        if (info->kinds & B_SYSTEM_MIXER) printf(" B_SYSTEM_MIXER");
        printf("\n");
        printf("  in_format_count = %" B_PRId32 "\n", info->in_format_count);
        printf("  out_format_count = %" B_PRId32 "\n", info->out_format_count);
}
#endif


// #pragma mark -


MediaAddonServer::MonitorHandler::MonitorHandler(MediaAddonServer* server)
{
        fServer = server;
}


void
MediaAddonServer::MonitorHandler::AddOnEnabled(const add_on_entry_info* info)
{
        entry_ref ref;
        make_entry_ref(info->dir_nref.device, info->dir_nref.node,
                info->name, &ref);
        BEntry entry(&ref, true);
        if (!entry.IsFile())
                return;

        BPath path(&ref);
        if (path.InitCheck() == B_OK)
                fServer->_AddOnAdded(path.Path(), info->nref.node);
}


void
MediaAddonServer::MonitorHandler::AddOnDisabled(const add_on_entry_info* info)
{
        fServer->_AddOnRemoved(info->nref.node);
}


// #pragma mark -


MediaAddonServer::MediaAddonServer(const char* signature)
        :
        BApplication(signature),
        fMonitorHandler(NULL),
        fPulseRunner(NULL),
        fStartup(true),
        fStartupSound(true),
        fSystemTimeSource(NULL)
{
        CALLED();
        fMediaRoster = BMediaRoster::Roster();
        fControlPort = create_port(64, MEDIA_ADDON_SERVER_PORT_NAME);
        fControlThread = spawn_thread(_ControlThread, "media_addon_server control",
                B_NORMAL_PRIORITY + 2, this);
        resume_thread(fControlThread);
}


MediaAddonServer::~MediaAddonServer()
{
        CALLED();

        delete_port(fControlPort);
        wait_for_thread(fControlThread, NULL);

        // unregister all media add-ons
        FileMap::iterator iterator = fFileMap.begin();
        for (; iterator != fFileMap.end(); iterator++)
                gDormantNodeManager->UnregisterAddOn(iterator->second);

        delete fMonitorHandler;
        delete fPulseRunner;
}


void
MediaAddonServer::ReadyToRun()
{
        if (!be_roster->IsRunning(B_MEDIA_SERVER_SIGNATURE)) {
                // the media server is not running, let's quit
                fprintf(stderr, "The media_server is not running!\n");
                Quit();
                return;
        }

        // the control thread is already running at this point,
        // so we can talk to the media server and also receive
        // commands for instantiation

        ASSERT(fStartup == true);

        // The very first thing to do is to create the system time source,
        // register it with the server, and make it the default SYSTEM_TIME_SOURCE
        fSystemTimeSource = new SystemTimeSource();
        status_t result = fMediaRoster->RegisterNode(fSystemTimeSource);
        if (result != B_OK) {
                fprintf(stderr, "Can't register system time source : %s\n",
                        strerror(result));
                ERROR("Can't register system time source");
        }

        if (fSystemTimeSource->ID() != NODE_SYSTEM_TIMESOURCE_ID)
                ERROR("System time source got wrong node ID");
        media_node node = fSystemTimeSource->Node();
        result = MediaRosterEx(fMediaRoster)->SetNode(SYSTEM_TIME_SOURCE, &node);
        if (result != B_OK)
                ERROR("Can't setup system time source as default");

        // During startup, first all add-ons are loaded, then all
        // nodes (flavors) representing physical inputs and outputs
        // are instantiated. Next, all add-ons that need autostart
        // will be autostarted. Finally, add-ons that don't have
        // any active nodes (flavors) will be unloaded.

        fMonitorHandler = new MonitorHandler(this);
        AddHandler(fMonitorHandler);

        BMessage pulse(B_PULSE);
        // the monitor handler needs a pulse to check if add-ons are ready
        fPulseRunner = new BMessageRunner(fMonitorHandler, &pulse, 1000000LL);

        result = fPulseRunner->InitCheck();
        if (result != B_OK)
                ERROR("Can't create the pulse runner");

        fMonitorHandler->AddAddOnDirectories("media");

#ifdef USER_ADDON_PATH
        node_ref nodeRef;
        if (entry.SetTo(USER_ADDON_PATH) == B_OK
                && entry.GetNodeRef(&nodeRef) == B_OK) {
                fMonitorHandler->AddDirectory(&nodeRef);
        }
#endif

        fStartup = false;

        InfoMap::iterator iterator = fInfoMap.begin();
        for (; iterator != fInfoMap.end(); iterator++)
                _InstantiatePhysicalInputsAndOutputs(iterator->second);

        for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
                _InstantiateAutostartFlavors(iterator->second);

        for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
                _PutAddonIfPossible(iterator->second);

        server_rescan_defaults_command cmd;
        SendToServer(SERVER_RESCAN_DEFAULTS, &cmd, sizeof(cmd));
}


bool
MediaAddonServer::QuitRequested()
{
        CALLED();

        InfoMap::iterator iterator = fInfoMap.begin();
        for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
                _DestroyInstantiatedFlavors(iterator->second);

        // the System timesource should be removed before we quit the roster
        if (fSystemTimeSource != NULL &&
                be_roster->IsRunning(B_MEDIA_SERVER_SIGNATURE)) {
                status_t result = fMediaRoster->UnregisterNode(fSystemTimeSource);
                if (result != B_OK) {
                        fprintf(stderr, "Error removing the system time source : %s\n",
                                strerror(result));
                        ERROR("Can't remove the system time source");
                }
                fSystemTimeSource->Release();
                fSystemTimeSource = NULL;
        }

        for (iterator = fInfoMap.begin(); iterator != fInfoMap.end(); iterator++)
                _PutAddonIfPossible(iterator->second);

        return true;
}


void
MediaAddonServer::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MEDIA_ADD_ON_SERVER_PLAY_MEDIA:
                {
                        const char* name;
                        const char* type;
                        if (message->FindString(MEDIA_NAME_KEY, &name) != B_OK
                                || message->FindString(MEDIA_TYPE_KEY, &type) != B_OK) {
                                message->SendReply(B_ERROR);
                                break;
                        }

                        PlayMediaFile(type, name);
                        message->SendReply((uint32)B_OK);
                        // TODO: don't know which reply is expected
                        return;
                }

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


void
MediaAddonServer::_HandleMessage(int32 code, const void* data, size_t size)
{
        switch (code) {
                case ADD_ON_SERVER_INSTANTIATE_DORMANT_NODE:
                {
                        const add_on_server_instantiate_dormant_node_request* request
                                = static_cast<
                                        const add_on_server_instantiate_dormant_node_request*>(
                                                data);
                        add_on_server_instantiate_dormant_node_reply reply;

                        status_t status
                                = MediaRosterEx(fMediaRoster)->InstantiateDormantNode(
                                        request->add_on_id, request->flavor_id,
                                        request->creator_team, &reply.node);
                        request->SendReply(status, &reply, sizeof(reply));
                        break;
                }

                case ADD_ON_SERVER_RESCAN_ADD_ON_FLAVORS:
                {
                        const add_on_server_rescan_flavors_command* command = static_cast<
                                const add_on_server_rescan_flavors_command*>(data);
                        BMediaAddOn* addOn
                                = gDormantNodeManager->GetAddOn(command->add_on_id);
                        if (addOn == NULL) {
                                ERROR("rescan flavors: Can't find a addon object for id %d\n",
                                        (int)command->add_on_id);
                                break;
                        }
                        _ScanAddOnFlavors(addOn);
                        gDormantNodeManager->PutAddOn(command->add_on_id);
                        break;
                }

                case ADD_ON_SERVER_RESCAN_FINISHED_NOTIFY:
                        if (fStartupSound) {
                                BMessage msg(MEDIA_ADD_ON_SERVER_PLAY_MEDIA);
                                msg.AddString(MEDIA_NAME_KEY, MEDIA_SOUNDS_STARTUP);
                                msg.AddString(MEDIA_TYPE_KEY, MEDIA_TYPE_SOUNDS);
                                BMessageRunner::StartSending(this, &msg, 2000000, 1);
                                fStartupSound = false;
                        }
                        break;

                default:
                        ERROR("media_addon_server: received unknown message code %#08"
                                B_PRIx32 "\n", code);
                        break;
        }
}


status_t
MediaAddonServer::_ControlThread(void* _server)
{
        MediaAddonServer* server = (MediaAddonServer*)_server;

        char data[B_MEDIA_MESSAGE_SIZE];
        ssize_t size;
        int32 code;
        while ((size = read_port_etc(server->_ControlPort(), &code, data,
                        sizeof(data), 0, 0)) > 0)
                server->_HandleMessage(code, data, size);

        return B_OK;
}


void
MediaAddonServer::_ScanAddOnFlavors(BMediaAddOn* addon)
{
        ASSERT(addon->AddonID() > 0);

        TRACE("MediaAddonServer::_ScanAddOnFlavors: id %" B_PRId32 "\n",
                addon->AddonID());

        // cache the media_addon_id in a local variable to avoid
        // calling BMediaAddOn::AddonID() too often
        media_addon_id addonID = addon->AddonID();

        // update the cached flavor count, get oldflavorcount and newflavorcount

        InfoMap::iterator found = fInfoMap.find(addonID);
        ASSERT(found != fInfoMap.end());

        AddOnInfo& info = found->second;
        int32 oldFlavorCount = info.flavor_count;
        int32 newFlavorCount = addon->CountFlavors();
        info.flavor_count = newFlavorCount;

        TRACE("%" B_PRId32 " old flavors, %" B_PRId32 " new flavors\n",
                oldFlavorCount, newFlavorCount);

        // during the first update (i == 0), the server removes old dormant_flavor_infos
        for (int i = 0; i < newFlavorCount; i++) {
                const flavor_info* flavorInfo;
                TRACE("flavor %d:\n", i);
                if (addon->GetFlavorAt(i, &flavorInfo) != B_OK) {
                        ERROR("MediaAddonServer::_ScanAddOnFlavors GetFlavorAt failed for "
                                "index %d!\n", i);
                        continue;
                }

#if DEBUG >= 2
                DumpFlavorInfo(flavorInfo);
#endif

                dormant_flavor_info dormantFlavorInfo;
                dormantFlavorInfo = *flavorInfo;
                dormantFlavorInfo.node_info.addon = addonID;
                dormantFlavorInfo.node_info.flavor_id = flavorInfo->internal_id;
                strlcpy(dormantFlavorInfo.node_info.name, flavorInfo->name,
                        B_MEDIA_NAME_LENGTH);

                size_t flattenedSize = dormantFlavorInfo.FlattenedSize();
                size_t messageSize = flattenedSize
                        + sizeof(server_register_dormant_node_command);
                server_register_dormant_node_command* message
                        = (server_register_dormant_node_command*)malloc(messageSize);
                if (message == NULL)
                        break;

                // The server should remove previously registered "dormant_flavor_info"s
                // during the first update, but after  the first iteration, we don't
                // want the server to anymore remove old dormant_flavor_infos
                message->purge_id = i == 0 ? addonID : 0;

                message->type = dormantFlavorInfo.TypeCode();
                message->flattened_size = flattenedSize;
                dormantFlavorInfo.Flatten(message->flattened_data, flattenedSize);

                status_t status = SendToServer(SERVER_REGISTER_DORMANT_NODE,
                        message, messageSize);
                if (status != B_OK) {
                        ERROR("MediaAddonServer::_ScanAddOnFlavors: couldn't register "
                                "dormant node: %s\n", strerror(status));
                }

                free(message);
        }

        // TODO: we currently pretend that all old flavors have been removed, this
        // could probably be done in a smarter way
        BPrivate::media::notifications::FlavorsChanged(addonID, newFlavorCount,
                oldFlavorCount);
}


void
MediaAddonServer::_AddOnAdded(const char* path, ino_t fileNode)
{
        TRACE("\n\nMediaAddonServer::_AddOnAdded: path %s\n", path);

        media_addon_id id = gDormantNodeManager->RegisterAddOn(path);
        if (id <= 0) {
                ERROR("MediaAddonServer::_AddOnAdded: failed to register add-on %s\n",
                        path);
                return;
        }

        TRACE("MediaAddonServer::_AddOnAdded: loading addon %" B_PRId32 " now..."
                "\n", id);

        BMediaAddOn* addon = gDormantNodeManager->GetAddOn(id);
        if (addon == NULL) {
                ERROR("MediaAddonServer::_AddOnAdded: failed to get add-on %s\n",
                        path);
                gDormantNodeManager->UnregisterAddOn(id);
                return;
        }

        TRACE("MediaAddonServer::_AddOnAdded: loading finished, id %" B_PRId32
                "\n", id);

        try {
                // put file's inode and addon's id into map
                fFileMap.insert(std::make_pair(fileNode, id));

                AddOnInfo info = {};
                fInfoMap.insert(std::make_pair(id, info));
        } catch (std::bad_alloc& exception) {
                fFileMap.erase(fileNode);
                return;
        }

        InfoMap::iterator found = fInfoMap.find(id);
        AddOnInfo& info = found->second;

        info.id = id;
        // temporary default
        info.wants_autostart = false;
        info.flavor_count = 0;
        info.addon = addon;

        // scan the flavors
        _ScanAddOnFlavors(addon);

        // need to call BMediaNode::WantsAutoStart()
        // after the flavors have been scanned
        info.wants_autostart = addon->WantsAutoStart();

        if (info.wants_autostart)
                TRACE("add-on %" B_PRId32 " WantsAutoStart!\n", id);

        // During startup, first all add-ons are loaded, then all
        // nodes (flavors) representing physical inputs and outputs
        // are instantiated. Next, all add-ons that need autostart
        // will be autostarted. Finally, add-ons that don't have
        // any active nodes (flavors) will be unloaded.

        // After startup is done, we simply do it for each new
        // loaded add-on, too.
        if (!fStartup) {
                _InstantiatePhysicalInputsAndOutputs(info);
                _InstantiateAutostartFlavors(info);
                _PutAddonIfPossible(info);

                // since something might have changed
                server_rescan_defaults_command cmd;
                SendToServer(SERVER_RESCAN_DEFAULTS, &cmd, sizeof(cmd));
        }

        // we do not call gDormantNodeManager->PutAddOn(id)
        // since it is done by _PutAddonIfPossible()
}


void
MediaAddonServer::_DestroyInstantiatedFlavors(AddOnInfo& info)
{
        printf("MediaAddonServer::_DestroyInstantiatedFlavors addon %" B_PRId32
                "\n", info.id);

        NodeVector::iterator iterator = info.active_flavors.begin();
        for (; iterator != info.active_flavors.end(); iterator++) {
                media_node& node = *iterator;

                printf("node %" B_PRId32 "\n", node.node);

                if ((node.kind & B_TIME_SOURCE) != 0
                        && (fMediaRoster->StopTimeSource(node, 0, true) != B_OK)) {
                        printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't stop "
                                "timesource\n");
                        continue;
                }

                if (fMediaRoster->StopNode(node, 0, true) != B_OK) {
                        printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't stop "
                                "node\n");
                        continue;
                }

                if ((node.kind & B_BUFFER_CONSUMER) != 0) {
                        media_input inputs[16];
                        int32 count = 0;
                        if (fMediaRoster->GetConnectedInputsFor(node, inputs, 16, &count)
                                        != B_OK) {
                                printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't "
                                        "get connected inputs\n");
                                continue;
                        }

                        for (int32 i = 0; i < count; i++) {
                                media_node_id sourceNode;
                                if ((sourceNode = fMediaRoster->NodeIDFor(
                                                inputs[i].source.port)) < 0) {
                                        printf("MediaAddonServer::_DestroyInstantiatedFlavors "
                                                "couldn't get source node id\n");
                                        continue;
                                }

                                if (fMediaRoster->Disconnect(sourceNode, inputs[i].source,
                                                node.node, inputs[i].destination) != B_OK) {
                                        printf("MediaAddonServer::_DestroyInstantiatedFlavors "
                                                "couldn't disconnect input\n");
                                        continue;
                                }
                        }
                }

                if ((node.kind & B_BUFFER_PRODUCER) != 0) {
                        media_output outputs[16];
                        int32 count = 0;
                        if (fMediaRoster->GetConnectedOutputsFor(node, outputs, 16,
                                        &count) != B_OK) {
                                printf("MediaAddonServer::_DestroyInstantiatedFlavors couldn't "
                                        "get connected outputs\n");
                                continue;
                        }

                        for (int32 i = 0; i < count; i++) {
                                media_node_id destNode;
                                if ((destNode = fMediaRoster->NodeIDFor(
                                                outputs[i].destination.port)) < 0) {
                                        printf("MediaAddonServer::_DestroyInstantiatedFlavors "
                                                "couldn't get destination node id\n");
                                        continue;
                                }

                                if (fMediaRoster->Disconnect(node.node, outputs[i].source,
                                                destNode, outputs[i].destination) != B_OK) {
                                        printf("MediaAddonServer::_DestroyInstantiatedFlavors "
                                                "couldn't disconnect output\n");
                                        continue;
                                }
                        }
                }

                if (MediaRosterEx(fMediaRoster)->ReleaseNodeAll(node) != B_OK) {
                        printf("MediaAddonServer::_DestroyInstantiatedFlavors "
                                                "couldn't release node\n");
                }

                // wait a bit to let the node clean up
                snooze(50000);
        }

        info.active_flavors.clear();
}


void
MediaAddonServer::_PutAddonIfPossible(AddOnInfo& info)
{
        if (info.addon && info.active_flavors.empty()) {
                gDormantNodeManager->PutAddOn(info.id);
                info.addon = NULL;
        }
}


void
MediaAddonServer::_InstantiatePhysicalInputsAndOutputs(AddOnInfo& info)
{
        CALLED();
        int32 count = info.addon->CountFlavors();

        for (int32 i = 0; i < count; i++) {
                const flavor_info* flavorinfo;
                if (info.addon->GetFlavorAt(i, &flavorinfo) != B_OK) {
                        ERROR("MediaAddonServer::InstantiatePhysialInputsAndOutputs "
                                "GetFlavorAt failed for index %" B_PRId32 "!\n", i);
                        continue;
                }
                if ((flavorinfo->kinds & (B_PHYSICAL_INPUT | B_PHYSICAL_OUTPUT)) != 0) {
                        media_node node;

                        dormant_node_info dormantNodeInfo;
                        dormantNodeInfo.addon = info.id;
                        dormantNodeInfo.flavor_id = flavorinfo->internal_id;
                        strcpy(dormantNodeInfo.name, flavorinfo->name);

                        TRACE("MediaAddonServer::InstantiatePhysialInputsAndOutputs: "
                                "\"%s\" is a physical input/output\n", flavorinfo->name);
                        status_t status = fMediaRoster->InstantiateDormantNode(
                                dormantNodeInfo, &node);
                        if (status != B_OK) {
                                ERROR("MediaAddonServer::InstantiatePhysialInputsAndOutputs "
                                        "Couldn't instantiate node flavor, internal_id %" B_PRId32
                                        ", name %s\n", flavorinfo->internal_id, flavorinfo->name);
                        } else {
                                TRACE("Node created!\n");
                                info.active_flavors.push_back(node);
                        }
                }
        }
}


void
MediaAddonServer::_InstantiateAutostartFlavors(AddOnInfo& info)
{
        if (!info.wants_autostart)
                return;

        for (int32 index = 0;; index++) {
                TRACE("trying autostart of node %" B_PRId32 ", index %" B_PRId32 "\n",
                        info.id, index);

                BMediaNode* node;
                int32 internalID;
                bool hasMore;
                status_t status = info.addon->AutoStart(index, &node, &internalID,
                        &hasMore);
                if (status == B_MEDIA_ADDON_FAILED && hasMore)
                        continue;
                else if (status != B_OK)
                        break;

                printf("started node %" B_PRId32 "\n", index);

                status = MediaRosterEx(fMediaRoster)->RegisterNode(node, info.id,
                        internalID);
                if (status != B_OK) {
                        ERROR("failed to register node %" B_PRId32 "\n", index);
                        node->Release();
                } else {
                        MediaRosterEx(fMediaRoster)->IncrementAddonFlavorInstancesCount(
                                info.id, internalID);
                        info.active_flavors.push_back(node->Node());
                }

                if (!hasMore)
                        return;
        }
}


void
MediaAddonServer::_AddOnRemoved(ino_t fileNode)
{
        // TODO: locking?

        FileMap::iterator foundFile = fFileMap.find(fileNode);
        if (foundFile == fFileMap.end()) {
                ERROR("MediaAddonServer::_AddOnRemoved: inode %" B_PRIdINO " removed, but no "
                        "media add-on found\n", fileNode);
                return;
        }

        media_addon_id id = foundFile->second;
        fFileMap.erase(foundFile);

        int32 oldFlavorCount;
        InfoMap::iterator foundInfo = fInfoMap.find(id);

        if (foundInfo == fInfoMap.end()) {
                ERROR("MediaAddonServer::_AddOnRemoved: couldn't get addon info for "
                        "add-on %" B_PRId32 "\n", id);
                oldFlavorCount = 1000;
        } else {
                AddOnInfo& info = foundInfo->second;
                oldFlavorCount = info.flavor_count;

                _DestroyInstantiatedFlavors(info);
                _PutAddonIfPossible(info);

                if (info.addon) {
                        ERROR("MediaAddonServer::_AddOnRemoved: couldn't unload addon "
                                "%" B_PRId32 " since flavors are in use\n", id);
                }

                fInfoMap.erase(foundInfo);
        }

        gDormantNodeManager->UnregisterAddOn(id);

        BPrivate::media::notifications::FlavorsChanged(id, 0, oldFlavorCount);
}


//      #pragma mark -


int
main()
{
        new MediaAddonServer(B_MEDIA_ADDON_SERVER_SIGNATURE);
        be_app->Run();
        delete be_app;
        return 0;
}