root/src/servers/media/MediaFilesManager.cpp
/*
 * Copyright 2003, Jérôme Duval. All rights reserved.
 * Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "MediaFilesManager.h"

#include <string.h>

#include <Application.h>
#include <Autolock.h>
#include <Directory.h>
#include <FindDirectory.h>
#include <MediaFiles.h>
#include <Path.h>

#include <MediaDebug.h>
#include <MediaSounds.h>


static const char* kSettingsDirectory = "Media";
static const char* kSettingsFile = "MediaFiles";
static const uint32 kSettingsWhat = 'mfil';


MediaFilesManager::MediaFilesManager()
        :
        BLocker("media files manager"),
        fSaveTimerRunner(NULL)
{
        CALLED();

        static const struct {
                const char* type;
                const char* item;
        } kInitialItems[] = {
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_BEEP},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_STARTUP},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_INFORMATION_ALERT},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_IMPORTANT_ALERT},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_ERROR_ALERT},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_KEY_DOWN},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_KEY_REPEAT},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_KEY_UP},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_MOUSE_DOWN},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_MOUSE_UP},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_ACTIVATED},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_CLOSE},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_MINIMIZED},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_OPEN},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_RESTORED},
                {MEDIA_TYPE_SOUNDS, MEDIA_SOUNDS_WINDOW_ZOOMED}
        };

        for (size_t i = 0; i < sizeof(kInitialItems) / sizeof(kInitialItems[0]);
                        i++) {
                _SetItem(kInitialItems[i].type, kInitialItems[i].item);
        }

        _LoadState();
#if DEBUG >=3
        Dump();
#endif
}


MediaFilesManager::~MediaFilesManager()
{
        CALLED();
        delete fSaveTimerRunner;
}


status_t
MediaFilesManager::SaveState()
{
        CALLED();
        BMessage settings(kSettingsWhat);
        status_t status;

        TypeMap::iterator iterator = fMap.begin();
        for (; iterator != fMap.end(); iterator++) {
                const BString& type = iterator->first;
                ItemMap& itemMap = iterator->second;

                BMessage items;
                status = items.AddString("type", type.String());
                if (status != B_OK)
                        return status;

                ItemMap::iterator itemIterator = itemMap.begin();
                for (; itemIterator != itemMap.end(); itemIterator++) {
                        const BString& item = itemIterator->first;
                        item_info& info = itemIterator->second;

                        status = items.AddString("item", item.String());
                        if (status == B_OK) {
                                BPath path(&info.ref);
                                status = items.AddString("path",
                                        path.Path() ? path.Path() : "");
                        }
                        if (status == B_OK)
                                status = items.AddFloat("gain", info.gain);
                        if (status != B_OK)
                                return status;
                }

                status = settings.AddMessage("type items", &items);
                if (status != B_OK)
                        return status;
        }

        BFile file;
        status = _OpenSettingsFile(file,
                B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
        if (status != B_OK)
                return status;

        return settings.Flatten(&file);
}


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

        printf("MediaFilesManager: types follow\n");

        TypeMap::iterator iterator = fMap.begin();
        for (; iterator != fMap.end(); iterator++) {
                const BString& type = iterator->first;
                ItemMap& itemMap = iterator->second;

                ItemMap::iterator fileIterator = itemMap.begin();
                for (; fileIterator != itemMap.end(); fileIterator++) {
                        const BString& item = fileIterator->first;
                        const item_info& info = fileIterator->second;

                        BPath path(&info.ref);

                        printf(" type \"%s\", item \"%s\", path \"%s\", gain %g\n",
                                type.String(), item.String(),
                                path.InitCheck() == B_OK ? path.Path() : "INVALID",
                                info.gain);
                }
        }

        printf("MediaFilesManager: list end\n");
}


area_id
MediaFilesManager::GetTypesArea(int32& count)
{
        CALLED();
        BAutolock _(this);

        count = fMap.size();

        size_t size = (count * B_MEDIA_NAME_LENGTH + B_PAGE_SIZE - 1)
                & ~(B_PAGE_SIZE - 1);

        char* start;
        area_id area = create_area("media types", (void**)&start, B_ANY_ADDRESS,
                size, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
        if (area < 0) {
                ERROR("MediaFilesManager::GetTypesArea(): failed to create area: %s\n",
                        strerror(area));
                count = 0;
                return area;
        }

        TypeMap::iterator iterator = fMap.begin();
        for (; iterator != fMap.end(); iterator++, start += B_MEDIA_NAME_LENGTH) {
                const BString& type = iterator->first;
                strncpy(start, type.String(), B_MEDIA_NAME_LENGTH);
        }

        return area;
}


area_id
MediaFilesManager::GetItemsArea(const char* type, int32& count)
{
        CALLED();
        if (type == NULL)
                return B_BAD_VALUE;

        BAutolock _(this);

        TypeMap::iterator found = fMap.find(BString(type));
        if (found == fMap.end()) {
                count = 0;
                return B_NAME_NOT_FOUND;
        }

        ItemMap& itemMap = found->second;
        count = itemMap.size();

        size_t size = (count * B_MEDIA_NAME_LENGTH + B_PAGE_SIZE - 1)
                & ~(B_PAGE_SIZE - 1);

        char* start;
        area_id area = create_area("media refs", (void**)&start, B_ANY_ADDRESS,
                size, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
        if (area < 0) {
                ERROR("MediaFilesManager::GetRefsArea(): failed to create area: %s\n",
                        strerror(area));
                count = 0;
                return area;
        }

        ItemMap::iterator iterator = itemMap.begin();
        for (; iterator != itemMap.end();
                        iterator++, start += B_MEDIA_NAME_LENGTH) {
                const BString& item = iterator->first;
                strncpy(start, item.String(), B_MEDIA_NAME_LENGTH);
        }

        return area;
}


status_t
MediaFilesManager::GetRefFor(const char* type, const char* item,
        entry_ref** _ref)
{
        CALLED();
        BAutolock _(this);

        item_info* info;
        status_t status = _GetItem(type, item, info);
        if (status == B_OK)
                *_ref = &info->ref;

        return status;
}


status_t
MediaFilesManager::GetAudioGainFor(const char* type, const char* item,
        float* _gain)
{
        CALLED();
        BAutolock _(this);

        item_info* info;
        status_t status = _GetItem(type, item, info);
        if (status == B_OK)
                *_gain = info->gain;

        return status;
}


status_t
MediaFilesManager::SetRefFor(const char* type, const char* item,
        const entry_ref& ref)
{
        CALLED();
        TRACE("MediaFilesManager::SetRefFor %s %s\n", type, item);

        BAutolock _(this);

        status_t status = _SetItem(type, item, &ref);
        if (status == B_OK)
                _LaunchTimer();

        return status;
}


status_t
MediaFilesManager::SetAudioGainFor(const char* type, const char* item,
        float gain)
{
        CALLED();
        TRACE("MediaFilesManager::SetAudioGainFor %s %s %g\n", type, item, gain);

        BAutolock _(this);

        status_t status = _SetItem(type, item, NULL, &gain);
        if (status == B_OK)
                _LaunchTimer();

        return status;
}


status_t
MediaFilesManager::InvalidateItem(const char* type, const char* item)
{
        CALLED();
        BAutolock _(this);

        TypeMap::iterator found = fMap.find(type);
        if (found == fMap.end())
                return B_NAME_NOT_FOUND;

        ItemMap& itemMap = found->second;
        itemMap[item] = item_info();

        _LaunchTimer();
        return B_OK;
}


status_t
MediaFilesManager::RemoveItem(const char *type, const char *item)
{
        CALLED();
        BAutolock _(this);

        TypeMap::iterator found = fMap.find(type);
        if (found == fMap.end())
                return B_NAME_NOT_FOUND;

        found->second.erase(item);
        if (found->second.empty())
                fMap.erase(found);

        _LaunchTimer();
        return B_OK;
}


void
MediaFilesManager::TimerMessage()
{
        SaveState();

        delete fSaveTimerRunner;
        fSaveTimerRunner = NULL;
}


void
MediaFilesManager::HandleAddSystemBeepEvent(BMessage* message)
{
        const char* name;
        const char* type;
        uint32 flags;
        if (message->FindString(MEDIA_NAME_KEY, &name) != B_OK
                || message->FindString(MEDIA_TYPE_KEY, &type) != B_OK
                || message->FindInt32(MEDIA_FLAGS_KEY, (int32 *)&flags) != B_OK) {
                message->SendReply(B_BAD_VALUE);
                return;
        }

        entry_ref* ref = NULL;
        if (GetRefFor(type, name, &ref) != B_OK) {
                entry_ref newRef;
                SetRefFor(type, name, newRef);
        }
}


void
MediaFilesManager::_LaunchTimer()
{
        if (fSaveTimerRunner == NULL) {
                BMessage timer(MEDIA_FILES_MANAGER_SAVE_TIMER);
                fSaveTimerRunner = new BMessageRunner(be_app, &timer, 3 * 1000000LL, 1);
        }
}


/*!     You need to have the manager locked when calling this method.
*/
status_t
MediaFilesManager::_GetItem(const char* type, const char* item,
        item_info*& info)
{
        ASSERT(IsLocked());

        TypeMap::iterator found = fMap.find(type);
        if (found == fMap.end())
                return B_NAME_NOT_FOUND;

        ItemMap::iterator foundFile = found->second.find(item);
        if (foundFile == found->second.end())
                return B_NAME_NOT_FOUND;

        info = &foundFile->second;
        return B_OK;
}


/*!     You need to have the manager locked when calling this method after
        launch.
*/
status_t
MediaFilesManager::_SetItem(const char* _type, const char* _item,
        const entry_ref* ref, const float* gain)
{
        CALLED();
        TRACE("MediaFilesManager::_SetItem(%s, %s)\n", _type, _item);

        BString type(_type);
        type.Truncate(B_MEDIA_NAME_LENGTH);
        BString item(_item);
        item.Truncate(B_MEDIA_NAME_LENGTH);

        try {
                TypeMap::iterator found = fMap.find(type);
                if (found == fMap.end()) {
                        // add new type
                        ItemMap itemMap;
                        // TODO: For some reason, this does not work:
                        //found = fMap.insert(TypeMap::value_type(type, itemMap));
                        fMap[type] = itemMap;
                        found = fMap.find(type);
                }

                ItemMap& itemMap = found->second;
                item_info info = itemMap[item];

                // only update what we've got
                if (gain != NULL)
                        info.gain = *gain;
                if (ref != NULL)
                        info.ref = *ref;

                itemMap[item] = info;
        } catch (std::bad_alloc& exception) {
                return B_NO_MEMORY;
        }

        return B_OK;
}


status_t
MediaFilesManager::_OpenSettingsFile(BFile& file, int mode)
{
        bool createFile = (mode & O_ACCMODE) != O_RDONLY;
        BPath path;
        status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
                createFile);
        if (status == B_OK)
                status = path.Append(kSettingsDirectory);
        if (status == B_OK && createFile) {
                status = create_directory(path.Path(),
                        S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
        }
        if (status == B_OK)
                status = path.Append(kSettingsFile);
        if (status != B_OK)
                return status;

        return file.SetTo(path.Path(), mode);
}


//! This is called by the media_server *before* any add-ons have been loaded.
status_t
MediaFilesManager::_LoadState()
{
        CALLED();

        BFile file;
        status_t status = _OpenSettingsFile(file, B_READ_ONLY);
        if (status != B_OK)
                return status;

        BMessage settings;
        status = settings.Unflatten(&file);
        if (status != B_OK)
                return status;

        if (settings.what != kSettingsWhat)
                return B_BAD_TYPE;

        BMessage items;
        for (int32 i = 0; settings.FindMessage("type items", i, &items) == B_OK;
                        i++) {
                const char* type;
                if (items.FindString("type", &type) != B_OK)
                        continue;

                const char* item;
                for (int32 j = 0; items.FindString("item", j, &item) == B_OK; j++) {
                        const char* path;
                        if (items.FindString("path", j, &path) != B_OK)
                                return B_BAD_DATA;

                        float gain;
                        if (items.FindFloat("gain", j, &gain) != B_OK)
                                gain = 1.0f;

                        entry_ref ref;
                        get_ref_for_path(path, &ref);
                                // it's okay for this to fail

                        _SetItem(type, item, &ref, &gain);
                }
        }

        return B_OK;
}