⛏️ index : haiku.git

/*
 * 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 <unicode/uversion.h>
#include <LocaleRosterData.h>

#include <Autolock.h>
#include <Catalog.h>
#include <Collator.h>
#include <Debug.h>
#include <DefaultCatalog.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <FormattingConventions.h>
#include <Language.h>
#include <Locale.h>
#include <Node.h>
#include <Path.h>
#include <PathFinder.h>
#include <Roster.h>
#include <String.h>
#include <StringList.h>
#include <TimeZone.h>

// ICU includes
#include <unicode/locid.h>
#include <unicode/timezone.h>


U_NAMESPACE_USE


namespace BPrivate {


// #pragma mark - CatalogAddOnInfo


CatalogAddOnInfo::CatalogAddOnInfo(const BString& name, const BString& path,
	uint8 priority)
	:
	fInstantiateFunc(NULL),
	fCreateFunc(NULL),
	fLanguagesFunc(NULL),
	fName(name),
	fPath(path),
	fAddOnImage(B_NO_INIT),
	fPriority(priority),
	fIsEmbedded(path.Length()==0)
{
}


CatalogAddOnInfo::~CatalogAddOnInfo()
{
	int32 count = fLoadedCatalogs.CountItems();
	for (int32 i = 0; i < count; ++i) {
		BCatalogData* cat
			= static_cast<BCatalogData*>(fLoadedCatalogs.ItemAt(i));
		delete cat;
	}
	fLoadedCatalogs.MakeEmpty();
	UnloadIfPossible();
}


bool
CatalogAddOnInfo::MakeSureItsLoaded()
{
	if (!fIsEmbedded && fAddOnImage < B_OK) {
		// add-on has not been loaded yet, so we try to load it:
		BString fullAddOnPath(fPath);
		fullAddOnPath << "/" << fName;
		fAddOnImage = load_add_on(fullAddOnPath.String());
		if (fAddOnImage >= B_OK) {
			get_image_symbol(fAddOnImage, "instantiate_catalog",
				B_SYMBOL_TYPE_TEXT, (void**)&fInstantiateFunc);
			get_image_symbol(fAddOnImage, "create_catalog",
				B_SYMBOL_TYPE_TEXT, (void**)&fCreateFunc);
			get_image_symbol(fAddOnImage, "get_available_languages",
				B_SYMBOL_TYPE_TEXT, (void**)&fLanguagesFunc);
		} else
			return false;
	} else if (fIsEmbedded) {
		// The built-in catalog still has to provide this function
		fLanguagesFunc = default_catalog_get_available_languages;
	}
	return true;
}


void
CatalogAddOnInfo::UnloadIfPossible()
{
	if (!fIsEmbedded && fLoadedCatalogs.IsEmpty()) {
		unload_add_on(fAddOnImage);
		fAddOnImage = B_NO_INIT;
		fInstantiateFunc = NULL;
		fCreateFunc = NULL;
		fLanguagesFunc = NULL;
	}
}


// #pragma mark - LocaleRosterData


namespace {


static const char* kPriorityAttr = "ADDON:priority";

static const char* kLanguageField = "language";
static const char* kTimezoneField = "timezone";
static const char* kTranslateFilesystemField = "filesys";


}	// anonymous namespace


LocaleRosterData::LocaleRosterData(const BLanguage& language,
	const BFormattingConventions& conventions)
	:
	fLock("LocaleRosterData"),
	fDefaultLocale(&language, &conventions),
	fIsFilesystemTranslationPreferred(true),
	fAreResourcesLoaded(false)
{
	fInitStatus = _Initialize();
}


LocaleRosterData::~LocaleRosterData()
{
	BAutolock lock(fLock);

	_CleanupCatalogAddOns();
}


status_t
LocaleRosterData::InitCheck() const
{
	return fAreResourcesLoaded ? B_OK : B_NO_INIT;
}


status_t
LocaleRosterData::Refresh()
{
	BAutolock lock(fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	_LoadLocaleSettings();
	_LoadTimeSettings();

	return B_OK;
}


int
LocaleRosterData::CompareInfos(const void* left, const void* right)
{
	const CatalogAddOnInfo* leftInfo
		= * static_cast<const CatalogAddOnInfo* const *>(left);
	const CatalogAddOnInfo* rightInfo
		= * static_cast<const CatalogAddOnInfo* const *>(right);

	return leftInfo->fPriority - rightInfo->fPriority;
}


status_t
LocaleRosterData::SetDefaultFormattingConventions(
	const BFormattingConventions& newFormattingConventions)
{
	status_t status = B_OK;

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

	status = _SetDefaultFormattingConventions(newFormattingConventions);

	if (status == B_OK)
		status = _SaveLocaleSettings();

	if (status == B_OK) {
		BMessage updateMessage(B_LOCALE_CHANGED);
		status = _AddDefaultFormattingConventionsToMessage(&updateMessage);
		if (status == B_OK)
			status = be_roster->Broadcast(&updateMessage);
	}

	return status;
}


status_t
LocaleRosterData::SetDefaultTimeZone(const BTimeZone& newZone)
{
	status_t status = B_OK;

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

	status = _SetDefaultTimeZone(newZone);

	if (status == B_OK)
		status = _SaveTimeSettings();

	if (status == B_OK) {
		BMessage updateMessage(B_LOCALE_CHANGED);
		status = _AddDefaultTimeZoneToMessage(&updateMessage);
		if (status == B_OK)
			status = be_roster->Broadcast(&updateMessage);
	}

	return status;
}


status_t
LocaleRosterData::SetPreferredLanguages(const BMessage* languages)
{
	status_t status = B_OK;

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

	status = _SetPreferredLanguages(languages);

	if (status == B_OK)
		status = _SaveLocaleSettings();

	if (status == B_OK) {
		BMessage updateMessage(B_LOCALE_CHANGED);
		status = _AddPreferredLanguagesToMessage(&updateMessage);
		if (status == B_OK)
			status = be_roster->Broadcast(&updateMessage);
	}

	return status;
}


status_t
LocaleRosterData::SetFilesystemTranslationPreferred(bool preferred)
{
	BAutolock lock(fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	_SetFilesystemTranslationPreferred(preferred);

	status_t status = _SaveLocaleSettings();

	if (status == B_OK) {
		BMessage updateMessage(B_LOCALE_CHANGED);
		status = _AddFilesystemTranslationPreferenceToMessage(&updateMessage);
		if (status == B_OK)
			status = be_roster->Broadcast(&updateMessage);
	}

	return status;
}


status_t
LocaleRosterData::GetResources(BResources** resources)
{
	if (resources == NULL)
		return B_BAD_VALUE;

	if (!fAreResourcesLoaded) {
		status_t result
			= fResources.SetToImage((const void*)&BLocaleRoster::Default);
		if (result != B_OK)
			return result;

		result = fResources.PreloadResourceType();
		if (result != B_OK)
			return result;

		fAreResourcesLoaded = true;
	}

	*resources = &fResources;
	return B_OK;
}


status_t
LocaleRosterData::_Initialize()
{
	status_t result = _InitializeCatalogAddOns();
	if (result != B_OK)
		return result;

	if ((result = Refresh()) != B_OK)
		return result;

	fInitStatus = B_OK;
	return B_OK;
}


/*
iterate over add-on-folders and collect information about each
catalog-add-ons (types of catalogs) into fCatalogAddOnInfos.
*/
status_t
LocaleRosterData::_InitializeCatalogAddOns()
{
	BAutolock lock(fLock);
	if (!lock.IsLocked())
		return B_ERROR;

	// add info about embedded default catalog:
	CatalogAddOnInfo* defaultCatalogAddOnInfo
		= new(std::nothrow) CatalogAddOnInfo("Default", "",
			DefaultCatalog::kDefaultCatalogAddOnPriority);
	if (!defaultCatalogAddOnInfo)
		return B_NO_MEMORY;

	defaultCatalogAddOnInfo->MakeSureItsLoaded();
	defaultCatalogAddOnInfo->fInstantiateFunc = DefaultCatalog::Instantiate;
	defaultCatalogAddOnInfo->fCreateFunc = DefaultCatalog::Create;
	fCatalogAddOnInfos.AddItem((void*)defaultCatalogAddOnInfo);

	BStringList folders;
	BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "locale/catalogs/",
		B_FIND_PATH_EXISTING_ONLY, folders);

	BPath addOnPath;
	BDirectory addOnFolder;
	char buf[4096];
	status_t err;
	for (int32 f = 0; f < folders.CountStrings(); f++) {
		BString addOnFolderName = folders.StringAt(f);
		err = addOnFolder.SetTo(addOnFolderName.String());
		if (err != B_OK)
			continue;

		// scan through all the folder's entries for catalog add-ons:
		int32 count;
		int8 priority;
		entry_ref eref;
		BNode node;
		BEntry entry;
		dirent* dent;
		while ((count = addOnFolder.GetNextDirents((dirent*)buf, sizeof(buf)))
				> 0) {
			dent = (dirent*)buf;
			while (count-- > 0) {
				if (strcmp(dent->d_name, ".") != 0
						&& strcmp(dent->d_name, "..") != 0
						&& strcmp(dent->d_name, "x86") != 0
						&& strcmp(dent->d_name, "x86_gcc2") != 0) {
					// we have found (what should be) a catalog-add-on:
					eref.device = dent->d_pdev;
					eref.directory = dent->d_pino;
					eref.set_name(dent->d_name);
					entry.SetTo(&eref, true);
						// traverse through any links to get to the real thang!
					node.SetTo(&entry);
					priority = -1;
					if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0,
						&priority, sizeof(int8)) <= 0) {
						// add-on has no priority-attribute yet, so we load it
						// to fetch the priority from the corresponding
						// symbol...
						BString fullAddOnPath(addOnFolderName);
						fullAddOnPath << "/" << dent->d_name;
						image_id image = load_add_on(fullAddOnPath.String());
						if (image >= B_OK) {
							uint8* prioPtr;
							if (get_image_symbol(image, "gCatalogAddOnPriority",
								B_SYMBOL_TYPE_DATA,
								(void**)&prioPtr) == B_OK) {
								priority = *prioPtr;
								node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0,
									&priority, sizeof(int8));
							}
							unload_add_on(image);
						}
					}

					if (priority >= 0) {
						// add-ons with priority < 0 will be ignored
						CatalogAddOnInfo* addOnInfo
							= new(std::nothrow) CatalogAddOnInfo(dent->d_name,
								addOnFolderName, priority);
						if (addOnInfo != NULL) {
							if (addOnInfo->MakeSureItsLoaded())
								fCatalogAddOnInfos.AddItem((void*)addOnInfo);
							else
								delete addOnInfo;
						}
					}
				}
				// Bump the dirent-pointer by length of the dirent just handled:
				dent = (dirent*)((char*)dent + dent->d_reclen);
			}
		}
	}
	fCatalogAddOnInfos.SortItems(CompareInfos);

	return B_OK;
}


/*
 * unloads all catalog-add-ons (which will throw away all loaded catalogs, too)
 */
void
LocaleRosterData::_CleanupCatalogAddOns()
{
	BAutolock lock(fLock);
	if (!lock.IsLocked())
		return;

	int32 count = fCatalogAddOnInfos.CountItems();
	for (int32 i = 0; i<count; ++i) {
		CatalogAddOnInfo* info
			= static_cast<CatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i));
		delete info;
	}
	fCatalogAddOnInfos.MakeEmpty();
}


status_t
LocaleRosterData::_LoadLocaleSettings()
{
	BPath path;
	BFile file;
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	if (status == B_OK) {
		path.Append("Locale settings");
		status = file.SetTo(path.Path(), B_READ_ONLY);
	}
	BMessage settings;
	if (status == B_OK)
		status = settings.Unflatten(&file);

	if (status == B_OK) {
		BFormattingConventions conventions(&settings);
		fDefaultLocale.SetFormattingConventions(conventions);

		_SetPreferredLanguages(&settings);

		bool preferred;
		if (settings.FindBool(kTranslateFilesystemField, &preferred) == B_OK)
			_SetFilesystemTranslationPreferred(preferred);

		return B_OK;
	}


	// Something went wrong (no settings file or invalid BMessage), so we
	// set everything to default values

	fPreferredLanguages.MakeEmpty();
	fPreferredLanguages.AddString(kLanguageField, "en");
	BLanguage defaultLanguage("en_US");
	fDefaultLocale.SetLanguage(defaultLanguage);
	BFormattingConventions conventions("en_US");
	fDefaultLocale.SetFormattingConventions(conventions);

	return status;
}


status_t
LocaleRosterData::_LoadTimeSettings()
{
	BPath path;
	BFile file;
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	if (status == B_OK) {
		path.Append("Time settings");
		status = file.SetTo(path.Path(), B_READ_ONLY);
	}
	BMessage settings;
	if (status == B_OK)
		status = settings.Unflatten(&file);
	if (status == B_OK) {
		BString timeZoneID;
		if (settings.FindString(kTimezoneField, &timeZoneID) == B_OK)
			_SetDefaultTimeZone(BTimeZone(timeZoneID.String()));
		else
			_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));

		return B_OK;
	}

	// Something went wrong (no settings file or invalid BMessage), so we
	// set everything to default values
	_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));

	return status;
}


status_t
LocaleRosterData::_SaveLocaleSettings()
{
	BMessage settings;
	status_t status = _AddDefaultFormattingConventionsToMessage(&settings);
	if (status == B_OK)
		_AddPreferredLanguagesToMessage(&settings);
	if (status == B_OK)
		_AddFilesystemTranslationPreferenceToMessage(&settings);

	BPath path;
	if (status == B_OK)
		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);

	BFile file;
	if (status == B_OK) {
		path.Append("Locale settings");
		status = file.SetTo(path.Path(),
			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
	}
	if (status == B_OK)
		status = settings.Flatten(&file);
	if (status == B_OK)
		status = file.Sync();

	return status;
}


status_t
LocaleRosterData::_SaveTimeSettings()
{
	BMessage settings;
	status_t status = _AddDefaultTimeZoneToMessage(&settings);

	BPath path;
	if (status == B_OK)
		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);

	BFile file;
	if (status == B_OK) {
		path.Append("Time settings");
		status = file.SetTo(path.Path(),
			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
	}
	if (status == B_OK)
		status = settings.Flatten(&file);
	if (status == B_OK)
		status = file.Sync();

	return status;
}


status_t
LocaleRosterData::_SetDefaultFormattingConventions(
	const BFormattingConventions& newFormattingConventions)
{
	fDefaultLocale.SetFormattingConventions(newFormattingConventions);

	UErrorCode icuError = U_ZERO_ERROR;
	Locale icuLocale = Locale::createCanonical(newFormattingConventions.ID());
	if (icuLocale.isBogus())
		return B_ERROR;

	Locale::setDefault(icuLocale, icuError);
	if (!U_SUCCESS(icuError))
		return B_ERROR;

	return B_OK;
}


status_t
LocaleRosterData::_SetDefaultTimeZone(const BTimeZone& newZone)
{
	fDefaultTimeZone = newZone;

	TimeZone* timeZone = TimeZone::createTimeZone(newZone.ID().String());
	if (timeZone == NULL)
		return B_ERROR;
	TimeZone::adoptDefault(timeZone);

	return B_OK;
}


status_t
LocaleRosterData::_SetPreferredLanguages(const BMessage* languages)
{
	BString langName;
	if (languages != NULL
		&& languages->FindString(kLanguageField, &langName) == B_OK) {
		fDefaultLocale.SetCollator(BCollator(langName.String()));
		fDefaultLocale.SetLanguage(BLanguage(langName.String()));

		fPreferredLanguages.RemoveName(kLanguageField);
		for (int i = 0; languages->FindString(kLanguageField, i, &langName)
				== B_OK; i++) {
			fPreferredLanguages.AddString(kLanguageField, langName);
		}
	} else {
		fPreferredLanguages.MakeEmpty();
		fPreferredLanguages.AddString(kLanguageField, "en");
		fDefaultLocale.SetCollator(BCollator("en"));
	}

	return B_OK;
}


void
LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred)
{
	fIsFilesystemTranslationPreferred = preferred;
}


status_t
LocaleRosterData::_AddDefaultFormattingConventionsToMessage(
	BMessage* message) const
{
	BFormattingConventions conventions;
	fDefaultLocale.GetFormattingConventions(&conventions);

	return conventions.Archive(message);
}


status_t
LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const
{
	return message->AddString(kTimezoneField, fDefaultTimeZone.ID());
}


status_t
LocaleRosterData::_AddPreferredLanguagesToMessage(BMessage* message) const
{
	status_t status = B_OK;

	BString langName;
	for (int i = 0; fPreferredLanguages.FindString("language", i,
			&langName) == B_OK; i++) {
		status = message->AddString(kLanguageField, langName);
		if (status != B_OK)
			break;
	}

	return status;
}


status_t
LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage(
	BMessage* message) const
{
	return message->AddBool(kTranslateFilesystemField,
		fIsFilesystemTranslationPreferred);
}


}	// namespace BPrivate