root/src/kits/locale/MutableLocaleRoster.cpp
/*
 * Copyright 2003-2012, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler, axeld@pinc-software.de
 *              Oliver Tappe, zooey@hirschkaefer.de
 */


#include <MutableLocaleRoster.h>

#include <pthread.h>

#include <Application.h>
#include <Autolock.h>
#include <Catalog.h>
#include <CatalogData.h>
#include <DefaultCatalog.h>
#include <Debug.h>
#include <Entry.h>
#include <FormattingConventions.h>
#include <Language.h>
#include <LocaleRosterData.h>
#include <String.h>


namespace BPrivate {


namespace {


static MutableLocaleRoster* sLocaleRoster;

static pthread_once_t sLocaleRosterInitOnce = PTHREAD_ONCE_INIT;


}       // anonymous namespace


static void
InitializeLocaleRoster()
{
        sLocaleRoster = new (std::nothrow) MutableLocaleRoster();
}


MutableLocaleRoster::MutableLocaleRoster()
{
}


MutableLocaleRoster::~MutableLocaleRoster()
{
}


/*static*/ MutableLocaleRoster*
MutableLocaleRoster::Default()
{
        if (sLocaleRoster == NULL)
                pthread_once(&sLocaleRosterInitOnce, &InitializeLocaleRoster);

        return sLocaleRoster;
}


status_t
MutableLocaleRoster::SetDefaultFormattingConventions(
        const BFormattingConventions& newFormattingConventions)
{
        return fData->SetDefaultFormattingConventions(newFormattingConventions);
}


status_t
MutableLocaleRoster::SetDefaultTimeZone(const BTimeZone& newZone)
{
        return fData->SetDefaultTimeZone(newZone);
}


status_t
MutableLocaleRoster::SetPreferredLanguages(const BMessage* languages)
{
        return fData->SetPreferredLanguages(languages);
}


status_t
MutableLocaleRoster::SetFilesystemTranslationPreferred(bool preferred)
{
        return fData->SetFilesystemTranslationPreferred(preferred);
}


status_t
MutableLocaleRoster::LoadSystemCatalog(BCatalog* catalog) const
{
        if (!catalog)
                return B_BAD_VALUE;

        // figure out libbe-image (shared object) by name
        image_info info;
        int32 cookie = 0;
        bool found = false;

        while (get_next_image_info(0, &cookie, &info) == B_OK) {
                if (info.data < (void*)&be_app
                        && (char*)info.data + info.data_size > (void*)&be_app) {
                        found = true;
                        break;
                }
        }

        if (!found)
                return B_ERROR;

        // load the catalog for libbe into the given catalog
        entry_ref ref;
        status_t status = BEntry(info.name).GetRef(&ref);
        if (status != B_OK)
                return status;

        return catalog->SetTo(ref);
}


/*
 * creates a new (empty) catalog of the given type (the request is dispatched
 * to the appropriate add-on).
 * If the add-on doesn't support catalog-creation or if the creation fails,
 * NULL is returned, otherwise a pointer to the freshly created catalog.
 * Any created catalog will be initialized with the given signature and
 * language-name.
 */
BCatalogData*
MutableLocaleRoster::CreateCatalog(const char* type, const char* signature,
        const char* language)
{
        if (!type || !signature || !language)
                return NULL;

        BAutolock lock(fData->fLock);
        if (!lock.IsLocked())
                return NULL;

        int32 count = fData->fCatalogAddOnInfos.CountItems();
        for (int32 i = 0; i < count; ++i) {
                CatalogAddOnInfo* info = (CatalogAddOnInfo*)
                        fData->fCatalogAddOnInfos.ItemAt(i);
                if (info->fName.ICompare(type) != 0 || !info->fCreateFunc)
                        continue;

                BCatalogData* catalog = info->fCreateFunc(signature, language);
                if (catalog != NULL) {
                        info->fLoadedCatalogs.AddItem(catalog);
                        return catalog;
                }
        }

        return NULL;
}


/*
 * Loads a catalog for the given signature, language and fingerprint.
 * The request to load this catalog is dispatched to all add-ons in turn,
 * until an add-on reports success.
 * If a catalog depends on another language (as 'english-british' depends
 * on 'english') the dependant catalogs are automatically loaded, too.
 * So it is perfectly possible that this method returns a catalog-chain
 * instead of a single catalog.
 * NULL is returned if no matching catalog could be found.
 */
BCatalogData*
MutableLocaleRoster::LoadCatalog(const entry_ref& catalogOwner,
        const char* language, int32 fingerprint) const
{
        BAutolock lock(fData->fLock);
        if (!lock.IsLocked())
                return NULL;

        int32 count = fData->fCatalogAddOnInfos.CountItems();
        for (int32 i = 0; i < count; ++i) {
                CatalogAddOnInfo* info = (CatalogAddOnInfo*)
                        fData->fCatalogAddOnInfos.ItemAt(i);

                if (!info->fInstantiateFunc)
                        continue;
                BMessage languages;
                if (language != NULL) {
                        // try to load catalogs for the given language:
                        languages.AddString("language", language);
                } else {
                        // try to load catalogs for one of the preferred languages:
                        GetPreferredLanguages(&languages);
                }

                BCatalogData* catalog = NULL;
                const char* lang;
                for (int32 l = 0; languages.FindString("language", l, &lang) == B_OK;
                        ++l) {
                        catalog = info->fInstantiateFunc(catalogOwner, lang, fingerprint);
                        if (catalog != NULL)
                                info->fLoadedCatalogs.AddItem(catalog);
                        // Chain-load catalogs for languages that depend on
                        // other languages.
                        // The current implementation uses the filename in order to
                        // detect dependencies (parenthood) between languages (it
                        // traverses from "english_british_oxford" to "english_british"
                        // to "english"):
                        int32 pos;
                        BString langName(lang);
                        BCatalogData* currentCatalog = catalog;
                        BCatalogData* nextCatalog = NULL;
                        while ((pos = langName.FindLast('_')) >= 0) {
                                // language is based on parent, so we load that, too:
                                // (even if the parent catalog was not found)
                                langName.Truncate(pos);
                                nextCatalog = info->fInstantiateFunc(catalogOwner,
                                        langName.String(), fingerprint);
                                if (nextCatalog != NULL) {
                                        info->fLoadedCatalogs.AddItem(nextCatalog);
                                        if (currentCatalog != NULL)
                                                currentCatalog->SetNext(nextCatalog);
                                        else
                                                catalog = nextCatalog;
                                        currentCatalog = nextCatalog;
                                }
                        }
                        if (catalog != NULL)
                                return catalog;
                }
        }

        return NULL;
}


/*
 * Loads a catalog for the given signature and language.
 *
 * Only the default catalog type is searched, and only the standard system
 * directories.
 *
 * If a catalog depends on another language (as 'english-british' depends
 * on 'english') the dependant catalogs are automatically loaded, too.
 * So it is perfectly possible that this method returns a catalog-chain
 * instead of a single catalog.
 * NULL is returned if no matching catalog could be found.
 */
BCatalogData*
MutableLocaleRoster::LoadCatalog(const char* signature,
        const char* language) const
{
        BAutolock lock(fData->fLock);
        if (!lock.IsLocked())
                return NULL;

        BMessage languages;
        if (language != NULL) {
                // try to load catalogs for the given language:
                languages.AddString("language", language);
        } else {
                // try to load catalogs for one of the preferred languages:
                GetPreferredLanguages(&languages);
        }


        int32 count = fData->fCatalogAddOnInfos.CountItems();
        CatalogAddOnInfo* defaultCatalogInfo = NULL;
        for (int32 i = 0; i < count; ++i) {
                CatalogAddOnInfo* info = (CatalogAddOnInfo*)
                        fData->fCatalogAddOnInfos.ItemAt(i);
                if (info->fInstantiateFunc
                                == BPrivate::DefaultCatalog::Instantiate) {
                        defaultCatalogInfo = info;
                        break;
                }
        }

        if (defaultCatalogInfo == NULL)
                return NULL;

        BPrivate::DefaultCatalog* catalog = NULL;
        const char* lang;
        for (int32 l = 0; languages.FindString("language", l, &lang) == B_OK; ++l) {
                catalog = new (std::nothrow) BPrivate::DefaultCatalog(NULL, signature,
                        lang);

                if (catalog != NULL) {
                        if (catalog->InitCheck() != B_OK
                                || catalog->ReadFromStandardLocations() != B_OK) {
                                delete catalog;
                                catalog = NULL;
                        } else {
                                defaultCatalogInfo->fLoadedCatalogs.AddItem(catalog);
                        }
                }

                // Chain-load catalogs for languages that depend on
                // other languages.
                // The current implementation uses the filename in order to
                // detect dependencies (parenthood) between languages (it
                // traverses from "english_british_oxford" to "english_british"
                // to "english"):
                int32 pos;
                BString langName(lang);
                BCatalogData* currentCatalog = catalog;
                BPrivate::DefaultCatalog* nextCatalog = NULL;
                while ((pos = langName.FindLast('_')) >= 0) {
                        // language is based on parent, so we load that, too:
                        // (even if the parent catalog was not found)
                        langName.Truncate(pos);
                        nextCatalog = new (std::nothrow) BPrivate::DefaultCatalog(NULL,
                                signature, langName.String());

                        if (nextCatalog == NULL)
                                continue;

                        if (nextCatalog->InitCheck() != B_OK
                                || nextCatalog->ReadFromStandardLocations() != B_OK) {
                                delete nextCatalog;
                                continue;
                        }

                        defaultCatalogInfo->fLoadedCatalogs.AddItem(nextCatalog);

                        if (currentCatalog != NULL)
                                currentCatalog->SetNext(nextCatalog);
                        else
                                catalog = nextCatalog;
                        currentCatalog = nextCatalog;
                }
                if (catalog != NULL)
                        return catalog;
        }

        return NULL;
}


/*
 * unloads the given catalog (or rather: catalog-chain).
 * Every single catalog of the chain will be deleted automatically.
 */
status_t
MutableLocaleRoster::UnloadCatalog(BCatalogData* catalog)
{
        if (!catalog)
                return B_BAD_VALUE;

        BAutolock lock(fData->fLock);
        if (!lock.IsLocked())
                return B_ERROR;

        status_t res = B_ERROR;
        BCatalogData* nextCatalog;

        while (catalog != NULL) {
                nextCatalog = catalog->Next();
                int32 count = fData->fCatalogAddOnInfos.CountItems();
                for (int32 i = 0; i < count; ++i) {
                        CatalogAddOnInfo* info = static_cast<CatalogAddOnInfo*>(
                                fData->fCatalogAddOnInfos.ItemAt(i));
                        if (info->fLoadedCatalogs.HasItem(catalog)) {
                                info->fLoadedCatalogs.RemoveItem(catalog);
                                delete catalog;
                                res = B_OK;
                                break;
                        }
                }
                catalog = nextCatalog;
        }
        return res;
}


}       // namespace BPrivate