root/src/kits/media/DormantNodeManager.cpp
/*
 * 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.
 *
 */


/*!     This is a management class for dormant media nodes.
        It is private to the media kit and only accessed by the BMediaRoster class
        and the media_addon_server.
        It handles loading/unloading of dormant nodes.

        Dormant media nodes can be instantiated on demand. The reside on harddisk in
        the directories /boot/beos/system/add-ons/media
        and /boot/home/config/add-ons/media.
        Multiple media nodes can be included in one file, they can be accessed using
        the BMediaAddOn that each file implements.
        The BMediaAddOn allows getting a list of supported flavors. Each flavor
        represents a media node.
        The media_addon_server does the initial scanning of files and getting the
        list of supported flavors. It uses the flavor_info to do this, and reports
        the list of flavors to the media_server packed into individual
        dormant_media_node structures.
*/


#include "DormantNodeManager.h"

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

#include <Autolock.h>
#include <Entry.h>
#include <Path.h>

#include <MediaDebug.h>
#include <MediaMisc.h>
#include <ServerInterface.h>
#include <DataExchange.h>


namespace BPrivate {
namespace media {


DormantNodeManager* gDormantNodeManager;
        // initialized by BMediaRoster.


DormantNodeManager::DormantNodeManager()
        :
        fLock("dormant node manager locker")
{
}


DormantNodeManager::~DormantNodeManager()
{
        // force unloading all currently loaded images

        AddOnMap::iterator iterator = fAddOnMap.begin();
        for (; iterator != fAddOnMap.end(); iterator++) {
                loaded_add_on_info& info = iterator->second;

                ERROR("Forcing unload of add-on id %" B_PRId32 " with usecount %"
                        B_PRId32 "\n", info.add_on->AddonID(), info.use_count);
                _UnloadAddOn(info.add_on, info.image);
        }
}


BMediaAddOn*
DormantNodeManager::GetAddOn(media_addon_id id)
{
        TRACE("DormantNodeManager::GetAddon, id %" B_PRId32 "\n", id);

        // first try to use a already loaded add-on
        BMediaAddOn* addOn = _LookupAddOn(id);
        if (addOn != NULL)
                return addOn;

        // Be careful, we avoid locking here!

        // ok, it's not loaded, try to get the path
        BPath path;
        if (FindAddOnPath(&path, id) != B_OK) {
                ERROR("DormantNodeManager::GetAddon: can't find path for add-on %"
                        B_PRId32 "\n", id);
                return NULL;
        }

        // try to load it
        BMediaAddOn* newAddOn;
        image_id image;
        if (_LoadAddOn(path.Path(), id, &newAddOn, &image) != B_OK) {
                ERROR("DormantNodeManager::GetAddon: can't load add-on %" B_PRId32
                        " from path %s\n",id, path.Path());
                return NULL;
        }

        // ok, we successfully loaded it. Now lock and insert it into the map,
        // or unload it if the map already contains one that was loaded by another
        // thread at the same time

        BAutolock _(fLock);

        addOn = _LookupAddOn(id);
        if (addOn == NULL) {
                // we use the loaded one
                addOn = newAddOn;

                // and save it into the list
                loaded_add_on_info info;
                info.add_on = newAddOn;
                info.image = image;
                info.use_count = 1;
                try {
                        fAddOnMap.insert(std::make_pair(id, info));
                } catch (std::bad_alloc& exception) {
                        _UnloadAddOn(newAddOn, image);
                        return NULL;
                }
        } else
                _UnloadAddOn(newAddOn, image);

        ASSERT(addOn->AddonID() == id);
        return addOn;
}


void
DormantNodeManager::PutAddOn(media_addon_id id)
{
        TRACE("DormantNodeManager::PutAddon, id %" B_PRId32 "\n", id);

        BAutolock locker(fLock);

        AddOnMap::iterator found = fAddOnMap.find(id);
        if (found == fAddOnMap.end()) {
                ERROR("DormantNodeManager::PutAddon: failed to find add-on %" B_PRId32
                        "\n", id);
                return;
        }

        loaded_add_on_info& info = found->second;

        if (--info.use_count == 0) {
                // unload add-on

                BMediaAddOn* addOn = info.add_on;
                image_id image = info.image;
                fAddOnMap.erase(found);

                locker.Unlock();
                _UnloadAddOn(addOn, image);
        }
}


void
DormantNodeManager::PutAddOnDelayed(media_addon_id id)
{
        // Called from a node destructor of the loaded media-add-on.
        // We must make sure that the media-add-on stays in memory
        // a couple of seconds longer.

        UNIMPLEMENTED();
}


//!     For use by media_addon_server only
media_addon_id
DormantNodeManager::RegisterAddOn(const char* path)
{
        TRACE("DormantNodeManager::RegisterAddon, path %s\n", path);

        entry_ref ref;
        status_t status = get_ref_for_path(path, &ref);
        if (status != B_OK) {
                ERROR("DormantNodeManager::RegisterAddon failed, couldn't get ref "
                        "for path %s: %s\n", path, strerror(status));
                return 0;
        }

        server_register_add_on_request request;
        request.ref = ref;

        server_register_add_on_reply reply;
        status = QueryServer(SERVER_REGISTER_ADD_ON, &request, sizeof(request),
                &reply, sizeof(reply));
        if (status != B_OK) {
                ERROR("DormantNodeManager::RegisterAddon failed, couldn't talk to "
                        "media server: %s\n", strerror(status));
                return 0;
        }

        TRACE("DormantNodeManager::RegisterAddon finished with id %" B_PRId32 "\n",
                reply.add_on_id);

        return reply.add_on_id;
}


//!     For use by media_addon_server only
void
DormantNodeManager::UnregisterAddOn(media_addon_id id)
{
        TRACE("DormantNodeManager::UnregisterAddon id %" B_PRId32 "\n", id);
        ASSERT(id > 0);

        port_id port = find_port(MEDIA_SERVER_PORT_NAME);
        if (port < 0)
                return;

        server_unregister_add_on_command msg;
        msg.add_on_id = id;
        write_port(port, SERVER_UNREGISTER_ADD_ON, &msg, sizeof(msg));
}


status_t
DormantNodeManager::FindAddOnPath(BPath* path, media_addon_id id)
{
        server_get_add_on_ref_request request;
        request.add_on_id = id;

        server_get_add_on_ref_reply reply;
        status_t status = QueryServer(SERVER_GET_ADD_ON_REF, &request,
                sizeof(request), &reply, sizeof(reply));
        if (status != B_OK)
                return status;

        entry_ref ref = reply.ref;
        return path->SetTo(&ref);
}


BMediaAddOn*
DormantNodeManager::_LookupAddOn(media_addon_id id)
{
        BAutolock _(fLock);

        AddOnMap::iterator found = fAddOnMap.find(id);
        if (found == fAddOnMap.end())
                return NULL;

        loaded_add_on_info& info = found->second;

        ASSERT(id == info.add_on->AddonID());
        info.use_count++;

        return info.add_on;
}


status_t
DormantNodeManager::_LoadAddOn(const char* path, media_addon_id id,
        BMediaAddOn** _newAddOn, image_id* _newImage)
{
        image_id image = load_add_on(path);
        if (image < 0) {
                ERROR("DormantNodeManager::LoadAddon: loading \"%s\" failed: %s\n",
                        path, strerror(image));
                return image;
        }

        BMediaAddOn* (*makeAddOn)(image_id);
        status_t status = get_image_symbol(image, "make_media_addon",
                B_SYMBOL_TYPE_TEXT, (void**)&makeAddOn);
        if (status != B_OK) {
                ERROR("DormantNodeManager::LoadAddon: loading failed, function not "
                        "found: %s\n", strerror(status));
                unload_add_on(image);
                return status;
        }

        BMediaAddOn* addOn = makeAddOn(image);
        if (addOn == NULL) {
                ERROR("DormantNodeManager::LoadAddon: creating BMediaAddOn failed\n");
                unload_add_on(image);
                return B_ERROR;
        }

        ASSERT(addOn->ImageID() == image);
                // this should be true for a well behaving add-ons

        // We are a friend class of BMediaAddOn and initialize these member
        // variables
        addOn->fAddon = id;
        addOn->fImage = image;

        // everything ok
        *_newAddOn = addOn;
        *_newImage = image;

        return B_OK;
}


void
DormantNodeManager::_UnloadAddOn(BMediaAddOn* addOn, image_id image)
{
        ASSERT(addOn != NULL);
        ASSERT(addOn->ImageID() == image);
                // if this fails, something bad happened to the add-on

        delete addOn;
        unload_add_on(image);
}


}       // namespace media
}       // namespace BPrivate