root/src/servers/media/NodeManager.cpp
/*
 * Copyright (c) 2015 Dario Casalinuovo
 * Copyright (c) 2002, 2003 Marcus Overhagen <Marcus@Overhagen.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files or portions
 * thereof (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 *  * 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
 *    in the  binary, as well as this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided with
 *    the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */


#include "NodeManager.h"

#include <Application.h>
#include <Autolock.h>
#include <Entry.h>
#include <MediaAddOn.h>
#include <MediaDefs.h>
#include <Message.h>
#include <Messenger.h>
#include <OS.h>
#include <Path.h>

#include <MediaDebug.h>
#include <MediaMisc.h>
#include <Notifications.h>

#include "AppManager.h"
#include "DefaultManager.h"
#include "media_server.h"


const char*
get_node_type(node_type type)
{
#define CASE(c) case c: return #c;
        switch (type) {
                CASE(VIDEO_INPUT)
                CASE(AUDIO_INPUT)
                CASE(VIDEO_OUTPUT)
                CASE(AUDIO_MIXER)
                CASE(AUDIO_OUTPUT)
                CASE(AUDIO_OUTPUT_EX)
                CASE(TIME_SOURCE)
                CASE(SYSTEM_TIME_SOURCE)

                default:
                        return "unknown";
        }
#undef CASE
}


// #pragma mark -


NodeManager::NodeManager()
        :
        BLocker("node manager"),
        fNextAddOnID(1),
        fNextNodeID(1),
        fDefaultManager(new DefaultManager)
{
}


NodeManager::~NodeManager()
{
        delete fDefaultManager;
}


// #pragma mark - Default node management


status_t
NodeManager::SetDefaultNode(node_type type, const media_node* node,
        const dormant_node_info* info, const media_input* input)
{
        BAutolock _(this);

        status_t status = B_BAD_VALUE;
        if (node != NULL)
                status = fDefaultManager->Set(node->node, NULL, 0, type);
        else if (input != NULL) {
                status = fDefaultManager->Set(input->node.node, input->name,
                        input->destination.id, type);
        } else if (info != NULL) {
                media_node_id nodeID;
                int32 count = 1;
                status = GetInstances(info->addon, info->flavor_id, &nodeID, &count,
                        count);
                if (status == B_OK)
                        status = fDefaultManager->Set(nodeID, NULL, 0, type);
        }

        if (status == B_OK && (type == VIDEO_INPUT || type == VIDEO_OUTPUT
                        || type == AUDIO_OUTPUT || type == AUDIO_INPUT)) {
                fDefaultManager->SaveState(this);
                Dump();
        }
        return status;
}


status_t
NodeManager::GetDefaultNode(node_type type, media_node_id* _nodeID,
        char* inputName, int32* _inputID)
{
        BAutolock _(this);
        return fDefaultManager->Get(_nodeID, inputName, _inputID, type);
}


status_t
NodeManager::RescanDefaultNodes()
{
        BAutolock _(this);
        return fDefaultManager->Rescan();
}


// #pragma mark - Live node management


status_t
NodeManager::RegisterNode(media_addon_id addOnID, int32 flavorID,
        const char* name, uint64 kinds, port_id port, team_id team,
        media_node_id timesource, media_node_id* _nodeID)
{
        BAutolock _(this);

        registered_node node;
        node.timesource_id = timesource;
        node.add_on_id = addOnID;
        node.flavor_id = flavorID;
        strlcpy(node.name, name, sizeof(node.name));
        node.kinds = kinds;
        node.port = port;
        node.containing_team = team;
        node.creator = -1; // will be set later
        node.ref_count = 1;

        if ((node.kinds & B_TIME_SOURCE) != 0
                        && strcmp(node.name, "System clock") == 0) {
                // This may happen when media_addon_server crash,
                // we will replace the old timesource.
                node.node_id = NODE_SYSTEM_TIMESOURCE_ID;

                NodeMap::iterator found = fNodeMap.find(node.node_id);
                if (found != fNodeMap.end())
                        fNodeMap.erase(node.node_id);

                *_nodeID = node.node_id;
        } else {
                node.node_id = fNextNodeID;
                *_nodeID = node.node_id;
        }

        try {
                node.team_ref_count.insert(std::make_pair(team, 1));
                fNodeMap.insert(std::make_pair(node.node_id, node));
        } catch (std::bad_alloc& exception) {
                return B_NO_MEMORY;
        }

        fNextNodeID++;

        TRACE("NodeManager::RegisterNode: node %" B_PRId32 ", addon_id %" B_PRId32
                ", flavor_id %" B_PRId32 ", name \"%s\", kinds %#" B_PRIx64", port %"
                B_PRId32 ", team %" B_PRId32 "\n", *_nodeID, addOnID, flavorID, name,
                kinds, port, team);
        return B_OK;
}


status_t
NodeManager::UnregisterNode(media_node_id id, team_id team,
        media_addon_id* _addOnID, int32* _flavorID)
{
        TRACE("NodeManager::UnregisterNode enter: node %" B_PRId32 ", team %"
                B_PRId32 "\n", id, team);

        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(id);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::UnregisterNode: couldn't find node %" B_PRId32 
                        " (team %" B_PRId32 ")\n", id, team);
                return B_ERROR;
        }

        registered_node& node = found->second;

        if (node.containing_team != team) {
                ERROR("NodeManager::UnregisterNode: team %" B_PRId32 " tried to "
                        "unregister node %" B_PRId32 ", but it was instantiated by team %"
                        B_PRId32 "\n", team, id, node.containing_team);
                return B_ERROR;
        }
        if (node.ref_count != 1) {
                ERROR("NodeManager::UnregisterNode: node %" B_PRId32 ", team %"
                        B_PRId32 " has ref count %" B_PRId32 " (should be 1)\n", id, team,
                        node.ref_count);
                //return B_ERROR;
        }

        if (_addOnID != NULL)
                *_addOnID = node.add_on_id;

        if (_flavorID != NULL)
                *_flavorID = node.flavor_id;

        fNodeMap.erase(found);

        TRACE("NodeManager::UnregisterNode leave: node %" B_PRId32 ", addon_id %"
                B_PRId32 ", flavor_id %" B_PRId32 " team %" B_PRId32 "\n", id,
                *_addOnID, *_flavorID, team);
        return B_OK;
}


status_t
NodeManager::ReleaseNodeReference(media_node_id id, team_id team)
{
        TRACE("NodeManager::ReleaseNodeReference enter: node %" B_PRId32 ", team %"
                B_PRId32 "\n", id, team);

        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(id);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::ReleaseNodeReference: node %" B_PRId32 " not "
                        "found\n", id);
                return B_ERROR;
        }

        registered_node& node = found->second;

        TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
        if (teamRef == node.team_ref_count.end()) {
                // Normally it is an error to release a node in another team. But we
                // make one exception: if the node is global, and the creator team
                // tries to release it, we will release it in the the
                // media_addon_server.
                team_id addOnServer = gAppManager->AddOnServerTeam();
                teamRef = node.team_ref_count.find(addOnServer);

                if (node.creator == team && teamRef != node.team_ref_count.end()) {
                        PRINT(1, "!!! NodeManager::ReleaseNodeReference doing global "
                                "release!\n");
                        node.creator = -1; // invalidate!
                        team = addOnServer;
                } else {
                        ERROR("NodeManager::ReleaseNodeReference: node %" B_PRId32 " has "
                                "no team %" B_PRId32 " references\n", id, team);
                        return B_ERROR;
                }
        }

#if DEBUG
        int32 teamCount = teamRef->second - 1;
        (void)teamCount;
#endif

        if (--teamRef->second == 0)
                node.team_ref_count.erase(teamRef);

        if (--node.ref_count == 0) {
                PRINT(1, "NodeManager::ReleaseNodeReference: detected released node is"
                        " now unused, node %" B_PRId32 "\n", id);

                // TODO: remove!
                node_final_release_command command;
                status_t status = SendToPort(node.port, NODE_FINAL_RELEASE, &command,
                        sizeof(command));
                if (status != B_OK) {
                        ERROR("NodeManager::ReleaseNodeReference: can't send command to "
                                "node %" B_PRId32 "\n", id);
                        // ignore error
                }
        }

        TRACE("NodeManager::ReleaseNodeReference leave: node %" B_PRId32 ", team %"
                B_PRId32 ", ref %" B_PRId32 ", team ref %" B_PRId32 "\n", id, team,
                node.ref_count, teamCount);
        return B_OK;
}


status_t
NodeManager::ReleaseNodeAll(media_node_id id)
{
        TRACE("NodeManager::ReleaseNodeAll enter: node %" B_PRId32 "\n", id);

        BAutolock _(this);
        
        NodeMap::iterator found = fNodeMap.find(id);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::ReleaseNodeAll: node %" B_PRId32 " not found\n",
                        id);
                return B_ERROR;
        }
        
        registered_node& node = found->second;
        node.team_ref_count.clear();
        node.ref_count = 0;
        
        node_final_release_command command;
        status_t status = SendToPort(node.port, NODE_FINAL_RELEASE, &command,
                sizeof(command));
        if (status != B_OK) {
                ERROR("NodeManager::ReleaseNodeAll: can't send command to "
                        "node %" B_PRId32 "\n", id);
                // ignore error
        }

        TRACE("NodeManager::ReleaseNodeAll leave: node %" B_PRId32 "\n", id);
        return B_OK;
}


status_t
NodeManager::SetNodeCreator(media_node_id id, team_id creator)
{
        TRACE("NodeManager::SetNodeCreator node %" B_PRId32 ", creator %" B_PRId32
                "\n", id, creator);

        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(id);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::SetNodeCreator: node %" B_PRId32 " not found\n",
                        id);
                return B_ERROR;
        }

        registered_node& node = found->second;

        if (node.creator != -1) {
                ERROR("NodeManager::SetNodeCreator: node %" B_PRId32 " is already"
                        " assigned creator %" B_PRId32 "\n", id, node.creator);
                return B_ERROR;
        }

        node.creator = creator;
        return B_OK;
}


status_t
NodeManager::GetCloneForID(media_node_id id, team_id team, media_node* node)
{
        TRACE("NodeManager::GetCloneForID enter: node %" B_PRId32 " team %"
                B_PRId32 "\n", id, team);

        BAutolock _(this);

        status_t status = _AcquireNodeReference(id, team);
        if (status != B_OK) {
                ERROR("NodeManager::GetCloneForID: couldn't increment ref count, "
                        "node %" B_PRId32 " team %" B_PRId32 "\n", id, team);
                return status;
        }

        NodeMap::iterator found = fNodeMap.find(id);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::GetCloneForID: node %" B_PRId32 " not found\n",
                        id);
                return B_ERROR;
        }

        registered_node& registeredNode = found->second;

        node->node = registeredNode.node_id;
        node->port = registeredNode.port;
        node->kind = registeredNode.kinds;

        TRACE("NodeManager::GetCloneForID leave: node %" B_PRId32 " team %"
                B_PRId32 "\n", id, team);
        return B_OK;
}


/*!     This function locates the default "node" for the requested "type" and
        returns a clone.
        If the requested type is AUDIO_OUTPUT_EX, also "input_name" and "input_id"
        need to be set and returned, as this is required by
        BMediaRoster::GetAudioOutput(media_node *out_node, int32 *out_input_id,
                BString *out_input_name).
*/
status_t
NodeManager::GetClone(node_type type, team_id team, media_node* node,
        char* inputName, int32* _inputID)
{
        BAutolock _(this);

        TRACE("NodeManager::GetClone enter: team %" B_PRId32 ", type %d (%s)\n",
                team, type, get_node_type(type));

        media_node_id id;
        status_t status = GetDefaultNode(type, &id, inputName, _inputID);
        if (status != B_OK) {
                ERROR("NodeManager::GetClone: couldn't GetDefaultNode, team %" B_PRId32
                        ", type %d (%s)\n", team, type, get_node_type(type));
                *node = media_node::null;
                return status;
        }
        ASSERT(id > 0);

        status = GetCloneForID(id, team, node);
        if (status != B_OK) {
                ERROR("NodeManager::GetClone: couldn't GetCloneForID, id %" B_PRId32
                        ", team %" B_PRId32 ", type %d (%s)\n", id, team, type,
                        get_node_type(type));
                *node = media_node::null;
                return status;
        }
        ASSERT(id == node->node);

        TRACE("NodeManager::GetClone leave: node id %" B_PRId32 ", node port %"
                B_PRId32 ", node kind %#" B_PRIx64 "\n", node->node, node->port,
                node->kind);
        return B_OK;
}


status_t
NodeManager::ReleaseNode(const media_node& node, team_id team)
{
        TRACE("NodeManager::ReleaseNode enter: node %" B_PRId32 " team %" B_PRId32
                "\n", node.node, team);

        if (ReleaseNodeReference(node.node, team) != B_OK) {
                ERROR("NodeManager::ReleaseNode: couldn't decrement node %" B_PRId32
                        " team %" B_PRId32 " ref count\n", node.node, team);
        }

        return B_OK;
}


status_t
NodeManager::PublishInputs(const media_node& node, const media_input* inputs,
        int32 count)
{
        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(node.node);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::PublishInputs: node %" B_PRId32 " not found\n",
                        node.node);
                return B_ERROR;
        }

        registered_node& registeredNode = found->second;

        registeredNode.input_list.clear();

        try {
                for (int32 i = 0; i < count; i++)
                        registeredNode.input_list.push_back(inputs[i]);
        } catch (std::bad_alloc& exception) {
                return B_NO_MEMORY;
        }

        return B_OK;
}


status_t
NodeManager::PublishOutputs(const media_node &node, const media_output* outputs,
        int32 count)
{
        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(node.node);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::PublishOutputs: node %" B_PRId32 " not found\n",
                        node.node);
                return B_ERROR;
        }

        registered_node& registeredNode = found->second;

        registeredNode.output_list.clear();

        try {
                for (int32 i = 0; i < count; i++)
                        registeredNode.output_list.push_back(outputs[i]);
        } catch (std::bad_alloc& exception) {
                return B_NO_MEMORY;
        }

        return B_OK;
}


status_t
NodeManager::FindNodeID(port_id port, media_node_id* _id)
{
        BAutolock _(this);

        NodeMap::iterator iterator = fNodeMap.begin();
        for (; iterator != fNodeMap.end(); iterator++) {
                registered_node& node = iterator->second;

                if (node.port == port) {
                        *_id = node.node_id;
                        TRACE("NodeManager::FindNodeID found port %" B_PRId32 ", node %"
                                B_PRId32 "\n", port, node.node_id);
                        return B_OK;
                }

                OutputList::iterator outIterator = node.output_list.begin();
                for (; outIterator != node.output_list.end(); outIterator++) {
                        if (outIterator->source.port == port) {
                                *_id = node.node_id;
                                TRACE("NodeManager::FindNodeID found output port %" B_PRId32
                                        ", node %" B_PRId32 "\n", port, node.node_id);
                                return B_OK;
                        }
                }

                InputList::iterator inIterator = node.input_list.begin();
                for (; inIterator != node.input_list.end(); inIterator++) {
                        if (inIterator->destination.port == port) {
                                *_id = node.node_id;
                                TRACE("NodeManager::FindNodeID found input port %" B_PRId32
                                        ", node %" B_PRId32 "\n", port, node.node_id);
                                return B_OK;
                        }
                }
        }

        ERROR("NodeManager::FindNodeID failed, port %" B_PRId32 "\n", port);
        return B_ERROR;
}


status_t
NodeManager::GetDormantNodeInfo(const media_node& node,
        dormant_node_info* nodeInfo)
{
        // TODO: not sure if this is correct
        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(node.node);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::GetDormantNodeInfo: node %" B_PRId32 " not found"
                        "\n", node.node);
                return B_ERROR;
        }

        registered_node& registeredNode = found->second;

        if (registeredNode.add_on_id == -1
                && node.node != NODE_SYSTEM_TIMESOURCE_ID) {
                // This function must return an error if the node is application owned
                TRACE("NodeManager::GetDormantNodeInfo NODE IS APPLICATION OWNED! "
                        "node %" B_PRId32 ", add_on_id %" B_PRId32 ", flavor_id %" B_PRId32
                        ", name \"%s\"\n", node.node, registeredNode.add_on_id,
                        registeredNode.flavor_id, registeredNode.name);
                return B_ERROR;
        }

        ASSERT(node.port == registeredNode.port);
        ASSERT((node.kind & NODE_KIND_COMPARE_MASK)
                == (registeredNode.kinds & NODE_KIND_COMPARE_MASK));

        nodeInfo->addon = registeredNode.add_on_id;
        nodeInfo->flavor_id = registeredNode.flavor_id;
        strlcpy(nodeInfo->name, registeredNode.name, sizeof(nodeInfo->name));

        TRACE("NodeManager::GetDormantNodeInfo node %" B_PRId32 ", add_on_id %"
                B_PRId32 ", flavor_id %" B_PRId32 ", name \"%s\"\n", node.node,
                registeredNode.add_on_id, registeredNode.flavor_id,
                registeredNode.name);
        return B_OK;
}


status_t
NodeManager::GetLiveNodeInfo(const media_node& node, live_node_info* liveInfo)
{
        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(node.node);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::GetLiveNodeInfo: node %" B_PRId32 " not found\n",
                        node.node);
                return B_ERROR;
        }

        registered_node& registeredNode = found->second;

        ASSERT(node.port == registeredNode.port);
        ASSERT((node.kind & NODE_KIND_COMPARE_MASK)
                == (registeredNode.kinds & NODE_KIND_COMPARE_MASK));

        liveInfo->node = node;
        liveInfo->hint_point = BPoint(0, 0);
        strlcpy(liveInfo->name, registeredNode.name, sizeof(liveInfo->name));

        TRACE("NodeManager::GetLiveNodeInfo node %" B_PRId32 ", name = \"%s\"\n",
                node.node, registeredNode.name);
        return B_OK;
}


status_t
NodeManager::GetInstances(media_addon_id addOnID, int32 flavorID,
        media_node_id* ids, int32* _count, int32 maxCount)
{
        BAutolock _(this);

        NodeMap::iterator iterator = fNodeMap.begin();
        int32 count = 0;
        for (; iterator != fNodeMap.end() && count < maxCount; iterator++) {
                registered_node& node = iterator->second;

                if (node.add_on_id == addOnID && node.flavor_id == flavorID)
                        ids[count++] = node.node_id;
        }

        TRACE("NodeManager::GetInstances found %" B_PRId32 " instances for "
                "addon_id %" B_PRId32 ", flavor_id %" B_PRId32 "\n", count, addOnID,
                flavorID);
        *_count = count;
        return B_OK;
}


status_t
NodeManager::GetLiveNodes(LiveNodeList& liveNodes, int32 maxCount,
        const media_format* inputFormat, const media_format* outputFormat,
        const char* name, uint64 requireKinds)
{
        TRACE("NodeManager::GetLiveNodes: maxCount %" B_PRId32 ", in-format %p, "
                "out-format %p, name %s, require kinds 0x%" B_PRIx64 "\n", maxCount,
                inputFormat, outputFormat, name != NULL ? name : "NULL", requireKinds);

        BAutolock _(this);

        // Determine the count of byte to compare when checking for a name with
        // or without wildcard
        size_t nameLength = 0;
        if (name != NULL) {
                nameLength = strlen(name);
                if (nameLength > 0 && name[nameLength - 1] == '*')
                        nameLength--;
        }

        NodeMap::iterator iterator = fNodeMap.begin();
        int32 count = 0;
        for (; iterator != fNodeMap.end() && count < maxCount; iterator++) {
                registered_node& node = iterator->second;

                if ((node.kinds & requireKinds) != requireKinds)
                        continue;

                if (nameLength != 0) {
                        if (strncmp(name, node.name, nameLength) != 0)
                                continue;
                }

                if (inputFormat != NULL) {
                        bool found = false;

                        for (InputList::iterator inIterator = node.input_list.begin();
                                        inIterator != node.input_list.end(); inIterator++) {
                                media_input& input = *inIterator;

                                if (format_is_compatible(*inputFormat, input.format)) {
                                        found = true;
                                        break;
                                }
                        }

                        if (!found)
                                continue;
                }

                if (outputFormat != NULL) {
                        bool found = false;

                        for (OutputList::iterator outIterator = node.output_list.begin();
                                        outIterator != node.output_list.end(); outIterator++) {
                                media_output& output = *outIterator;

                                if (format_is_compatible(*outputFormat, output.format)) {
                                        found = true;
                                        break;
                                }
                        }

                        if (!found)
                                continue;
                }

                live_node_info info;
                info.node.node = node.node_id;
                info.node.port = node.port;
                info.node.kind = node.kinds;
                info.hint_point = BPoint(0, 0);
                strlcpy(info.name, node.name, sizeof(info.name));

                try {
                        liveNodes.push_back(info);
                } catch (std::bad_alloc& exception) {
                        return B_NO_MEMORY;
                }

                count++;
        }

        TRACE("NodeManager::GetLiveNodes found %" B_PRId32 "\n", count);
        return B_OK;
}


/*!     Add media_node_id of all live nodes to the message
        int32 "media_node_id" (multiple items)
*/
status_t
NodeManager::GetLiveNodes(BMessage* message)
{
        BAutolock _(this);

        NodeMap::iterator iterator = fNodeMap.begin();
        for (; iterator != fNodeMap.end(); iterator++) {
                registered_node& node = iterator->second;

                if (message->AddInt32("media_node_id", node.node_id) != B_OK)
                        return B_NO_MEMORY;
        }

        return B_OK;
}


// #pragma mark - Registration of BMediaAddOns


void
NodeManager::RegisterAddOn(const entry_ref& ref, media_addon_id* _newID)
{
        BAutolock _(this);

        media_addon_id id = fNextAddOnID++;

//      printf("NodeManager::RegisterAddOn: ref-name \"%s\", assigning id %" 
//              B_PRId32 "\n", ref.name, id);

        try {
                fPathMap.insert(std::make_pair(id, ref));
                *_newID = id;
        } catch (std::bad_alloc& exception) {
                *_newID = -1;
        }
}


void
NodeManager::UnregisterAddOn(media_addon_id addOnID)
{
        PRINT(1, "NodeManager::UnregisterAddOn: id %" B_PRId32 "\n", addOnID);

        BAutolock _(this);

        RemoveDormantFlavorInfo(addOnID);
        fPathMap.erase(addOnID);
}


status_t
NodeManager::GetAddOnRef(media_addon_id addOnID, entry_ref* ref)
{
        BAutolock _(this);

        PathMap::iterator found = fPathMap.find(addOnID);
        if (found == fPathMap.end())
                return B_ERROR;

        *ref = found->second;
        return B_OK;
}


// #pragma mark - Registration of node flavors, published by BMediaAddOns


//!     This function is only used (indirectly) by the media_addon_server.
status_t
NodeManager::AddDormantFlavorInfo(const dormant_flavor_info& flavorInfo)
{
        PRINT(1, "NodeManager::AddDormantFlavorInfo, addon-id %" B_PRId32 ", "
                "flavor-id %" B_PRId32 ", name \"%s\", flavor-name \"%s\", flavor-info"
                " \"%s\"\n", flavorInfo.node_info.addon,
                flavorInfo.node_info.flavor_id, flavorInfo.node_info.name,
                flavorInfo.name, flavorInfo.info);

        BAutolock _(this);

        // Try to find the addon-id/flavor-id in the list.
        // If it already exists, update the info, but don't change its instance
        // count.

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                if (info.add_on_id != flavorInfo.node_info.addon
                        || info.flavor_id != flavorInfo.node_info.flavor_id)
                        continue;

                if (info.info_valid) {
                        ERROR("NodeManager::AddDormantFlavorInfo, addon-id %" B_PRId32 ", "
                                "flavor-id %" B_PRId32 " does already exist\n",
                                info.info.node_info.addon, info.info.node_info.flavor_id);
                }

                TRACE("NodeManager::AddDormantFlavorInfo, updating addon-id %" B_PRId32
                        ", flavor-id %" B_PRId32 "\n", info.info.node_info.addon,
                        info.info.node_info.flavor_id);

                info.max_instances_count = flavorInfo.possible_count > 0
                        ? flavorInfo.possible_count : INT32_MAX;
                info.info_valid = true;
                info.info = flavorInfo;
                return B_OK;
        }

        // Insert information into the list

        dormant_add_on_flavor_info info;
        info.add_on_id = flavorInfo.node_info.addon;
        info.flavor_id = flavorInfo.node_info.flavor_id;
        info.max_instances_count = flavorInfo.possible_count > 0
                ? flavorInfo.possible_count : INT32_MAX;
        info.instances_count = 0;
        info.info_valid = true;
        info.info = flavorInfo;

        try {
                fDormantFlavors.push_back(info);
        } catch (std::bad_alloc& exception) {
                return B_NO_MEMORY;
        }

        return B_OK;
}


//!     This function is only used (indirectly) by the media_addon_server
void
NodeManager::InvalidateDormantFlavorInfo(media_addon_id addOnID)
{
        BAutolock _(this);

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                if (info.add_on_id == addOnID && info.info_valid) {
                        PRINT(1, "NodeManager::InvalidateDormantFlavorInfo, addon-id %"
                                B_PRId32 ", flavor-id %" B_PRId32 ", name \"%s\", flavor-name "
                                "\"%s\", flavor-info \"%s\"\n", info.info.node_info.addon,
                                info.info.node_info.flavor_id, info.info.node_info.name,
                                info.info.name, info.info.info);

                        info.info_valid = false;
                }
        }
}


//!     This function is only used (indirectly) by the media_addon_server
void
NodeManager::RemoveDormantFlavorInfo(media_addon_id addOnID)
{
        BAutolock _(this);

        for (size_t index = 0; index < fDormantFlavors.size(); index++) {
                dormant_add_on_flavor_info& info = fDormantFlavors[index];

                if (info.add_on_id == addOnID) {
                        PRINT(1, "NodeManager::RemoveDormantFlavorInfo, addon-id %"
                                B_PRId32 ", flavor-id %" B_PRId32 ", name \"%s\", flavor-name "
                                "\"%s\", flavor-info \"%s\"\n", info.info.node_info.addon,
                                info.info.node_info.flavor_id, info.info.node_info.name,
                                info.info.name, info.info.info);
                        fDormantFlavors.erase(fDormantFlavors.begin() + index--);
                }
        }
}


status_t
NodeManager::IncrementFlavorInstancesCount(media_addon_id addOnID,
        int32 flavorID, team_id team)
{
        BAutolock _(this);

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                if (info.add_on_id != addOnID || info.flavor_id != flavorID)
                        continue;

                if (info.instances_count >= info.max_instances_count) {
                        // maximum (or more) instances already exist
                        ERROR("NodeManager::IncrementFlavorInstancesCount addon-id %"
                                B_PRId32 ", flavor-id %" B_PRId32 " maximum (or more) "
                                "instances already exist\n", addOnID, flavorID);
                        return B_ERROR;
                }

                TeamCountMap::iterator teamInstance
                        = info.team_instances_count.find(team);
                if (teamInstance == info.team_instances_count.end()) {
                        // This is the team's first instance
                        try {
                                info.team_instances_count.insert(std::make_pair(team, 1));
                        } catch (std::bad_alloc& exception) {
                                return B_NO_MEMORY;
                        }
                } else {
                        // Just increase its ref count
                        teamInstance->second++;
                }

                info.instances_count++;
                return B_OK;
        }

        ERROR("NodeManager::IncrementFlavorInstancesCount addon-id %" B_PRId32 ", "
                "flavor-id %" B_PRId32 " not found\n", addOnID, flavorID);
        return B_ERROR;
}


status_t
NodeManager::DecrementFlavorInstancesCount(media_addon_id addOnID,
        int32 flavorID, team_id team)
{
        BAutolock _(this);

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                if (info.add_on_id != addOnID || info.flavor_id != flavorID)
                        continue;

                TeamCountMap::iterator teamInstance
                        = info.team_instances_count.find(team);
                if (teamInstance == info.team_instances_count.end()) {
                        ERROR("NodeManager::DecrementFlavorInstancesCount addon-id %"
                                B_PRId32 ", flavor-id %" B_PRId32 " team %" B_PRId32 " has no "
                                "references\n", addOnID, flavorID, team);
                        return B_ERROR;
                }
                if (--teamInstance->second == 0)
                        info.team_instances_count.erase(teamInstance);

                info.instances_count--;
                return B_OK;
        }

        ERROR("NodeManager::DecrementFlavorInstancesCount addon-id %" B_PRId32 ", "
                "flavor-id %" B_PRId32 " not found\n", addOnID, flavorID);
        return B_ERROR;
}


//!     This function is called when the media_addon_server has crashed
void
NodeManager::CleanupDormantFlavorInfos()
{
        PRINT(1, "NodeManager::CleanupDormantFlavorInfos\n");

        BAutolock _(this);

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                // Current instance count is zero since the media_addon_server crashed.
                BPrivate::media::notifications::FlavorsChanged(info.add_on_id,
                        0, info.instances_count);
        }

        fDormantFlavors.clear();

        PRINT(1, "NodeManager::CleanupDormantFlavorInfos done\n");
}


status_t
NodeManager::GetDormantNodes(dormant_node_info* infos, int32* _count,
        const media_format* input, const media_format* output, const char* name,
        uint64 requireKinds, uint64 denyKinds)
{
        BAutolock _(this);

        // Determine the count of byte to compare when checking for a name with
        // or without wildcard
        size_t nameLength = 0;
        if (name != NULL) {
                nameLength = strlen(name);
                if (nameLength > 0 && name[nameLength - 1] == '*')
                        nameLength--;
        }

        int32 maxCount = *_count;
        int32 count = 0;

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end() && count < maxCount; iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                if (!info.info_valid)
                        continue;

                if ((info.info.kinds & requireKinds) != requireKinds
                        || (info.info.kinds & denyKinds) != 0)
                        continue;

                if (nameLength != 0) {
                        if (strncmp(name, info.info.name, nameLength) != 0)
                                continue;
                }

                if (input != NULL) {
                        bool found = false;

                        for (int32 i = 0; i < info.info.in_format_count; i++) {
                                if (format_is_compatible(*input, info.info.in_formats[i])) {
                                        found = true;
                                        break;
                                }
                        }

                        if (!found)
                                continue;
                }

                if (output != NULL) {
                        bool found = false;

                        for (int32 i = 0; i < info.info.out_format_count; i++) {
                                if (format_is_compatible(*output, info.info.out_formats[i])) {
                                        found = true;
                                        break;
                                }
                        }

                        if (!found)
                                continue;
                }

                infos[count++] = info.info.node_info;
        }

        *_count = count;
        return B_OK;
}


status_t
NodeManager::GetDormantFlavorInfoFor(media_addon_id addOnID, int32 flavorID,
        dormant_flavor_info* flavorInfo)
{
        BAutolock _(this);

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& info = *iterator;

                if (info.add_on_id == addOnID && info.flavor_id == flavorID
                        && info.info_valid) {
                        *flavorInfo = info.info;
                        return B_OK;
                }
        }

        return B_ERROR;
}


// #pragma mark - Misc.


status_t
NodeManager::SetNodeTimeSource(media_node_id node,
        media_node_id timesource)
{
        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(node);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::SetNodeTimeSource: node %"
                        B_PRId32 " not found\n", node);
                return B_ERROR;
        }
        registered_node& registeredNode = found->second;
        registeredNode.timesource_id = timesource;
        return B_OK;
}


void
NodeManager::CleanupTeam(team_id team)
{
        BAutolock _(this);

        fDefaultManager->CleanupTeam(team);

        PRINT(1, "NodeManager::CleanupTeam: team %" B_PRId32 "\n", team);

        // Cleanup node references

        for (NodeMap::iterator iterator = fNodeMap.begin();
                        iterator != fNodeMap.end();) {
                registered_node& node = iterator->second;
                NodeMap::iterator remove = iterator++;

                // If the gone team was the creator of some global dormant node
                // instance, we now invalidate that we may want to remove that
                // global node, but I'm not sure
                if (node.creator == team) {
                        node.creator = -1;
                        // fall through
                }

                // If the team hosting this node is gone, remove node from database
                if (node.containing_team == team) {
                        PRINT(1, "NodeManager::CleanupTeam: removing node id %" B_PRId32
                                ", team %" B_PRId32 "\n", node.node_id, team);
                        // Ensure the slave node is removed from it's timesource
                        _NotifyTimeSource(node);
                        fNodeMap.erase(remove);
                        BPrivate::media::notifications::NodesDeleted(&node.node_id, 1);
                        continue;
                }

                // Check the list of teams that have references to this node, and
                // remove the team
                TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
                if (teamRef != node.team_ref_count.end()) {
                        PRINT(1, "NodeManager::CleanupTeam: removing %" B_PRId32 " refs "
                                "from node id %" B_PRId32 ", team %" B_PRId32 "\n",
                                teamRef->second, node.node_id, team);
                        node.ref_count -= teamRef->second;
                        if (node.ref_count == 0) {
                                PRINT(1, "NodeManager::CleanupTeam: removing node id %"
                                        B_PRId32 " that has no teams\n", node.node_id);

                                // Ensure the slave node is removed from it's timesource
                                _NotifyTimeSource(node);
                                fNodeMap.erase(remove);
                                BPrivate::media::notifications::NodesDeleted(&node.node_id, 1);
                        } else
                                node.team_ref_count.erase(teamRef);
                }
        }

        // Cleanup add-on references

        for (size_t index = 0; index < fDormantFlavors.size(); index++) {
                dormant_add_on_flavor_info& flavorInfo = fDormantFlavors[index];

                TeamCountMap::iterator instanceCount
                        = flavorInfo.team_instances_count.find(team);
                if (instanceCount != flavorInfo.team_instances_count.end()) {
                        PRINT(1, "NodeManager::CleanupTeam: removing %" B_PRId32 " "
                                "instances from addon %" B_PRId32 ", flavor %" B_PRId32 "\n",
                                instanceCount->second, flavorInfo.add_on_id,
                                flavorInfo.flavor_id);

                        int32 count = flavorInfo.instances_count;
                        flavorInfo.instances_count -= instanceCount->second;
                        if (flavorInfo.instances_count <= 0) {
                                fDormantFlavors.erase(fDormantFlavors.begin() + index--);
                                BPrivate::media::notifications::FlavorsChanged(
                                        flavorInfo.add_on_id, 0, count);
                        } else
                                flavorInfo.team_instances_count.erase(team);
                }
        }
}


status_t
NodeManager::LoadState()
{
        BAutolock _(this);
        return fDefaultManager->LoadState();
}

status_t
NodeManager::SaveState()
{
        BAutolock _(this);
        return fDefaultManager->SaveState(this);
}


void
NodeManager::Dump()
{
        BAutolock _(this);

        // for each addon-id, the add-on path map contains an entry_ref

        printf("\nNodeManager: addon path map follows:\n");

        for (PathMap::iterator iterator = fPathMap.begin();
                        iterator != fPathMap.end(); iterator++) {
                BPath path(&iterator->second);
                printf(" addon-id %" B_PRId32 ", path \"%s\"\n", iterator->first,
                        path.InitCheck() == B_OK ? path.Path() : "INVALID");
        }

        printf("NodeManager: list end\n\n");

        // for each node-id, the registered node map contians information about
        // source of the node, users, etc.

        printf("NodeManager: registered nodes map follows:\n");
        for (NodeMap::iterator iterator = fNodeMap.begin();
                        iterator != fNodeMap.end(); iterator++) {
                registered_node& node = iterator->second;

                printf("  node-id %" B_PRId32 ", addon-id %" B_PRId32 ", addon-flavor-"
                        "id %" B_PRId32 ", port %" B_PRId32 ", creator %" B_PRId32 ", "
                        "team %" B_PRId32 ", kinds %#08" B_PRIx64 ", name \"%s\", "
                        "ref_count %" B_PRId32 "\n", node.node_id, node.add_on_id,
                        node.flavor_id, node.port, node.creator, node.containing_team, 
                        node.kinds, node.name, node.ref_count);

                printf("    teams (refcount): ");
                for (TeamCountMap::iterator refsIterator = node.team_ref_count.begin();
                                refsIterator != node.team_ref_count.end(); refsIterator++) {
                        printf("%" B_PRId32 " (%" B_PRId32 "), ", refsIterator->first, 
                                refsIterator->second);
                }
                printf("\n");

                for (InputList::iterator inIterator = node.input_list.begin();
                                inIterator != node.input_list.end(); inIterator++) {
                        media_input& input = *inIterator;
                        printf("    media_input: node-id %" B_PRId32 ", node-port %"
                                B_PRId32 ", source-port %" B_PRId32 ", source-id  %" B_PRId32
                                ", dest-port %" B_PRId32 ", dest-id %" B_PRId32 ", name "
                                "\"%s\"\n", input.node.node, input.node.port, input.source.port,
                                input.source.id, input.destination.port, input.destination.id,
                                input.name);
                }
                if (node.input_list.empty())
                        printf("    media_input: none\n");

                for (OutputList::iterator outIterator = node.output_list.begin();
                                outIterator != node.output_list.end(); outIterator++) {
                        media_output& output = *outIterator;
                        printf("    media_output: node-id %" B_PRId32 ", node-port %"
                                B_PRId32 ", source-port %" B_PRId32 ", source-id  %" B_PRId32
                                ", dest-port %" B_PRId32 ", dest-id %" B_PRId32 ", name "
                                "\"%s\"\n", output.node.node, output.node.port,
                                output.source.port, output.source.id, output.destination.port,
                                output.destination.id, output.name);
                }
                if (node.output_list.empty())
                        printf("    media_output: none\n");
        }

        printf("NodeManager: list end\n");
        printf("\n");

        // Dormant add-on flavors

        printf("NodeManager: dormant flavor list follows:\n");

        for (DormantFlavorList::iterator iterator = fDormantFlavors.begin();
                        iterator != fDormantFlavors.end(); iterator++) {
                dormant_add_on_flavor_info& flavorInfo = *iterator;

                printf("  addon-id %" B_PRId32 ", flavor-id %" B_PRId32 ", max "
                        "instances count %" B_PRId32 ", instances count %" B_PRId32 ", "
                        "info valid %s\n", flavorInfo.add_on_id, flavorInfo.flavor_id,
                        flavorInfo.max_instances_count, flavorInfo.instances_count,
                        flavorInfo.info_valid ? "yes" : "no");
                printf("    teams (instances): ");
                for (TeamCountMap::iterator countIterator
                                        = flavorInfo.team_instances_count.begin();
                                countIterator != flavorInfo.team_instances_count.end();
                                countIterator++) {
                        printf("%" B_PRId32 " (%" B_PRId32 "), ", countIterator->first,
                                countIterator->second);
                }
                printf("\n");
                if (!flavorInfo.info_valid)
                        continue;

                printf("    addon-id %" B_PRId32 ", addon-flavor-id %" B_PRId32 ", "
                        "addon-name \"%s\"\n", flavorInfo.info.node_info.addon,
                        flavorInfo.info.node_info.flavor_id,
                        flavorInfo.info.node_info.name);
                printf("    flavor-kinds %#08" B_PRIx64 ", flavor_flags %#08" B_PRIx32
                        ", internal_id %" B_PRId32 ", possible_count %" B_PRId32 ", "
                        "in_format_count %" B_PRId32 ", out_format_count %" B_PRId32 "\n",
                        flavorInfo.info.kinds, flavorInfo.info.flavor_flags,
                        flavorInfo.info.internal_id, flavorInfo.info.possible_count,
                        flavorInfo.info.in_format_count, flavorInfo.info.out_format_count);
                printf("    flavor-name \"%s\"\n", flavorInfo.info.name);
                printf("    flavor-info \"%s\"\n", flavorInfo.info.info);
        }
        printf("NodeManager: list end\n");

        fDefaultManager->Dump();
}


// #pragma mark - private methods


status_t
NodeManager::_AcquireNodeReference(media_node_id id, team_id team)
{
        TRACE("NodeManager::_AcquireNodeReference enter: node %" B_PRId32 ", team "
                "%" B_PRId32 "\n", id, team);

        BAutolock _(this);

        NodeMap::iterator found = fNodeMap.find(id);
        if (found == fNodeMap.end()) {
                ERROR("NodeManager::_AcquireNodeReference: node %" B_PRId32 " not "
                        "found\n", id);
                return B_ERROR;
        }

        registered_node& node = found->second;

        TeamCountMap::iterator teamRef = node.team_ref_count.find(team);
        if (teamRef == node.team_ref_count.end()) {
                // This is the team's first reference
                try {
                        node.team_ref_count.insert(std::make_pair(team, 1));
                } catch (std::bad_alloc& exception) {
                        return B_NO_MEMORY;
                }
        } else {
                // Just increase its ref count
                teamRef->second++;
        }

        node.ref_count++;

        TRACE("NodeManager::_AcquireNodeReference leave: node %" B_PRId32 ", team "
                "%" B_PRId32 ", ref %" B_PRId32 ", team ref %" B_PRId32 "\n", id, team,
                node.ref_count, node.team_ref_count.find(team)->second);
        return B_OK;
}


void
NodeManager::_NotifyTimeSource(registered_node& node)
{
        team_id team = be_app->Team();
        media_node timeSource;
        // Ensure the timesource ensure still exists
        if (GetCloneForID(node.timesource_id, team, &timeSource) != B_OK)
                return;

        media_node currentNode;
        if (GetCloneForID(node.node_id, team,
                &currentNode) == B_OK) {
                timesource_remove_slave_node_command cmd;
                cmd.node = currentNode;
                // Notify slave node removal to owner timesource
                SendToPort(timeSource.port, TIMESOURCE_REMOVE_SLAVE_NODE,
                        &cmd, sizeof(cmd));
                ReleaseNode(timeSource, team);
        }
        ReleaseNode(currentNode, team);
}