1/*
2 * Copyright 2003-2012, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 *		Oliver Tappe, zooey@hirschkaefer.de
8 */
9
10
11#include <unicode/uversion.h>
12#include <LocaleRosterData.h>
13
14#include <Autolock.h>
15#include <Catalog.h>
16#include <Collator.h>
17#include <Debug.h>
18#include <DefaultCatalog.h>
19#include <Directory.h>
20#include <Entry.h>
21#include <File.h>
22#include <FindDirectory.h>
23#include <FormattingConventions.h>
24#include <Language.h>
25#include <Locale.h>
26#include <Node.h>
27#include <Path.h>
28#include <PathFinder.h>
29#include <Roster.h>
30#include <String.h>
31#include <StringList.h>
32#include <TimeZone.h>
33
34// ICU includes
35#include <unicode/locid.h>
36#include <unicode/timezone.h>
37
38
39U_NAMESPACE_USE
40
41
42namespace BPrivate {
43
44
45// #pragma mark - CatalogAddOnInfo
46
47
48CatalogAddOnInfo::CatalogAddOnInfo(const BString& name, const BString& path,
49	uint8 priority)
50	:
51	fInstantiateFunc(NULL),
52	fCreateFunc(NULL),
53	fLanguagesFunc(NULL),
54	fName(name),
55	fPath(path),
56	fAddOnImage(B_NO_INIT),
57	fPriority(priority),
58	fIsEmbedded(path.Length()==0)
59{
60}
61
62
63CatalogAddOnInfo::~CatalogAddOnInfo()
64{
65	int32 count = fLoadedCatalogs.CountItems();
66	for (int32 i = 0; i < count; ++i) {
67		BCatalogData* cat
68			= static_cast<BCatalogData*>(fLoadedCatalogs.ItemAt(i));
69		delete cat;
70	}
71	fLoadedCatalogs.MakeEmpty();
72	UnloadIfPossible();
73}
74
75
76bool
77CatalogAddOnInfo::MakeSureItsLoaded()
78{
79	if (!fIsEmbedded && fAddOnImage < B_OK) {
80		// add-on has not been loaded yet, so we try to load it:
81		BString fullAddOnPath(fPath);
82		fullAddOnPath << "/" << fName;
83		fAddOnImage = load_add_on(fullAddOnPath.String());
84		if (fAddOnImage >= B_OK) {
85			get_image_symbol(fAddOnImage, "instantiate_catalog",
86				B_SYMBOL_TYPE_TEXT, (void**)&fInstantiateFunc);
87			get_image_symbol(fAddOnImage, "create_catalog",
88				B_SYMBOL_TYPE_TEXT, (void**)&fCreateFunc);
89			get_image_symbol(fAddOnImage, "get_available_languages",
90				B_SYMBOL_TYPE_TEXT, (void**)&fLanguagesFunc);
91		} else
92			return false;
93	} else if (fIsEmbedded) {
94		// The built-in catalog still has to provide this function
95		fLanguagesFunc = default_catalog_get_available_languages;
96	}
97	return true;
98}
99
100
101void
102CatalogAddOnInfo::UnloadIfPossible()
103{
104	if (!fIsEmbedded && fLoadedCatalogs.IsEmpty()) {
105		unload_add_on(fAddOnImage);
106		fAddOnImage = B_NO_INIT;
107		fInstantiateFunc = NULL;
108		fCreateFunc = NULL;
109		fLanguagesFunc = NULL;
110	}
111}
112
113
114// #pragma mark - LocaleRosterData
115
116
117namespace {
118
119
120static const char* kPriorityAttr = "ADDON:priority";
121
122static const char* kLanguageField = "language";
123static const char* kTimezoneField = "timezone";
124static const char* kTranslateFilesystemField = "filesys";
125
126
127}	// anonymous namespace
128
129
130LocaleRosterData::LocaleRosterData(const BLanguage& language,
131	const BFormattingConventions& conventions)
132	:
133	fLock("LocaleRosterData"),
134	fDefaultLocale(&language, &conventions),
135	fIsFilesystemTranslationPreferred(true),
136	fAreResourcesLoaded(false)
137{
138	fInitStatus = _Initialize();
139}
140
141
142LocaleRosterData::~LocaleRosterData()
143{
144	BAutolock lock(fLock);
145
146	_CleanupCatalogAddOns();
147}
148
149
150status_t
151LocaleRosterData::InitCheck() const
152{
153	return fAreResourcesLoaded ? B_OK : B_NO_INIT;
154}
155
156
157status_t
158LocaleRosterData::Refresh()
159{
160	BAutolock lock(fLock);
161	if (!lock.IsLocked())
162		return B_ERROR;
163
164	_LoadLocaleSettings();
165	_LoadTimeSettings();
166
167	return B_OK;
168}
169
170
171int
172LocaleRosterData::CompareInfos(const void* left, const void* right)
173{
174	const CatalogAddOnInfo* leftInfo
175		= * static_cast<const CatalogAddOnInfo* const *>(left);
176	const CatalogAddOnInfo* rightInfo
177		= * static_cast<const CatalogAddOnInfo* const *>(right);
178
179	return leftInfo->fPriority - rightInfo->fPriority;
180}
181
182
183status_t
184LocaleRosterData::SetDefaultFormattingConventions(
185	const BFormattingConventions& newFormattingConventions)
186{
187	status_t status = B_OK;
188
189	BAutolock lock(fLock);
190	if (!lock.IsLocked())
191		return B_ERROR;
192
193	status = _SetDefaultFormattingConventions(newFormattingConventions);
194
195	if (status == B_OK)
196		status = _SaveLocaleSettings();
197
198	if (status == B_OK) {
199		BMessage updateMessage(B_LOCALE_CHANGED);
200		status = _AddDefaultFormattingConventionsToMessage(&updateMessage);
201		if (status == B_OK)
202			status = be_roster->Broadcast(&updateMessage);
203	}
204
205	return status;
206}
207
208
209status_t
210LocaleRosterData::SetDefaultTimeZone(const BTimeZone& newZone)
211{
212	status_t status = B_OK;
213
214	BAutolock lock(fLock);
215	if (!lock.IsLocked())
216		return B_ERROR;
217
218	status = _SetDefaultTimeZone(newZone);
219
220	if (status == B_OK)
221		status = _SaveTimeSettings();
222
223	if (status == B_OK) {
224		BMessage updateMessage(B_LOCALE_CHANGED);
225		status = _AddDefaultTimeZoneToMessage(&updateMessage);
226		if (status == B_OK)
227			status = be_roster->Broadcast(&updateMessage);
228	}
229
230	return status;
231}
232
233
234status_t
235LocaleRosterData::SetPreferredLanguages(const BMessage* languages)
236{
237	status_t status = B_OK;
238
239	BAutolock lock(fLock);
240	if (!lock.IsLocked())
241		return B_ERROR;
242
243	status = _SetPreferredLanguages(languages);
244
245	if (status == B_OK)
246		status = _SaveLocaleSettings();
247
248	if (status == B_OK) {
249		BMessage updateMessage(B_LOCALE_CHANGED);
250		status = _AddPreferredLanguagesToMessage(&updateMessage);
251		if (status == B_OK)
252			status = be_roster->Broadcast(&updateMessage);
253	}
254
255	return status;
256}
257
258
259status_t
260LocaleRosterData::SetFilesystemTranslationPreferred(bool preferred)
261{
262	BAutolock lock(fLock);
263	if (!lock.IsLocked())
264		return B_ERROR;
265
266	_SetFilesystemTranslationPreferred(preferred);
267
268	status_t status = _SaveLocaleSettings();
269
270	if (status == B_OK) {
271		BMessage updateMessage(B_LOCALE_CHANGED);
272		status = _AddFilesystemTranslationPreferenceToMessage(&updateMessage);
273		if (status == B_OK)
274			status = be_roster->Broadcast(&updateMessage);
275	}
276
277	return status;
278}
279
280
281status_t
282LocaleRosterData::GetResources(BResources** resources)
283{
284	if (resources == NULL)
285		return B_BAD_VALUE;
286
287	if (!fAreResourcesLoaded) {
288		status_t result
289			= fResources.SetToImage((const void*)&BLocaleRoster::Default);
290		if (result != B_OK)
291			return result;
292
293		result = fResources.PreloadResourceType();
294		if (result != B_OK)
295			return result;
296
297		fAreResourcesLoaded = true;
298	}
299
300	*resources = &fResources;
301	return B_OK;
302}
303
304
305status_t
306LocaleRosterData::_Initialize()
307{
308	status_t result = _InitializeCatalogAddOns();
309	if (result != B_OK)
310		return result;
311
312	if ((result = Refresh()) != B_OK)
313		return result;
314
315	fInitStatus = B_OK;
316	return B_OK;
317}
318
319
320/*
321iterate over add-on-folders and collect information about each
322catalog-add-ons (types of catalogs) into fCatalogAddOnInfos.
323*/
324status_t
325LocaleRosterData::_InitializeCatalogAddOns()
326{
327	BAutolock lock(fLock);
328	if (!lock.IsLocked())
329		return B_ERROR;
330
331	// add info about embedded default catalog:
332	CatalogAddOnInfo* defaultCatalogAddOnInfo
333		= new(std::nothrow) CatalogAddOnInfo("Default", "",
334			DefaultCatalog::kDefaultCatalogAddOnPriority);
335	if (!defaultCatalogAddOnInfo)
336		return B_NO_MEMORY;
337
338	defaultCatalogAddOnInfo->MakeSureItsLoaded();
339	defaultCatalogAddOnInfo->fInstantiateFunc = DefaultCatalog::Instantiate;
340	defaultCatalogAddOnInfo->fCreateFunc = DefaultCatalog::Create;
341	fCatalogAddOnInfos.AddItem((void*)defaultCatalogAddOnInfo);
342
343	BStringList folders;
344	BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "locale/catalogs/",
345		B_FIND_PATH_EXISTING_ONLY, folders);
346
347	BPath addOnPath;
348	BDirectory addOnFolder;
349	char buf[4096];
350	status_t err;
351	for (int32 f = 0; f < folders.CountStrings(); f++) {
352		BString addOnFolderName = folders.StringAt(f);
353		err = addOnFolder.SetTo(addOnFolderName.String());
354		if (err != B_OK)
355			continue;
356
357		// scan through all the folder's entries for catalog add-ons:
358		int32 count;
359		int8 priority;
360		entry_ref eref;
361		BNode node;
362		BEntry entry;
363		dirent* dent;
364		while ((count = addOnFolder.GetNextDirents((dirent*)buf, sizeof(buf)))
365				> 0) {
366			dent = (dirent*)buf;
367			while (count-- > 0) {
368				if (strcmp(dent->d_name, ".") != 0
369						&& strcmp(dent->d_name, "..") != 0
370						&& strcmp(dent->d_name, "x86") != 0
371						&& strcmp(dent->d_name, "x86_gcc2") != 0) {
372					// we have found (what should be) a catalog-add-on:
373					eref.device = dent->d_pdev;
374					eref.directory = dent->d_pino;
375					eref.set_name(dent->d_name);
376					entry.SetTo(&eref, true);
377						// traverse through any links to get to the real thang!
378					node.SetTo(&entry);
379					priority = -1;
380					if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0,
381						&priority, sizeof(int8)) <= 0) {
382						// add-on has no priority-attribute yet, so we load it
383						// to fetch the priority from the corresponding
384						// symbol...
385						BString fullAddOnPath(addOnFolderName);
386						fullAddOnPath << "/" << dent->d_name;
387						image_id image = load_add_on(fullAddOnPath.String());
388						if (image >= B_OK) {
389							uint8* prioPtr;
390							if (get_image_symbol(image, "gCatalogAddOnPriority",
391								B_SYMBOL_TYPE_DATA,
392								(void**)&prioPtr) == B_OK) {
393								priority = *prioPtr;
394								node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0,
395									&priority, sizeof(int8));
396							}
397							unload_add_on(image);
398						}
399					}
400
401					if (priority >= 0) {
402						// add-ons with priority < 0 will be ignored
403						CatalogAddOnInfo* addOnInfo
404							= new(std::nothrow) CatalogAddOnInfo(dent->d_name,
405								addOnFolderName, priority);
406						if (addOnInfo != NULL) {
407							if (addOnInfo->MakeSureItsLoaded())
408								fCatalogAddOnInfos.AddItem((void*)addOnInfo);
409							else
410								delete addOnInfo;
411						}
412					}
413				}
414				// Bump the dirent-pointer by length of the dirent just handled:
415				dent = (dirent*)((char*)dent + dent->d_reclen);
416			}
417		}
418	}
419	fCatalogAddOnInfos.SortItems(CompareInfos);
420
421	return B_OK;
422}
423
424
425/*
426 * unloads all catalog-add-ons (which will throw away all loaded catalogs, too)
427 */
428void
429LocaleRosterData::_CleanupCatalogAddOns()
430{
431	BAutolock lock(fLock);
432	if (!lock.IsLocked())
433		return;
434
435	int32 count = fCatalogAddOnInfos.CountItems();
436	for (int32 i = 0; i<count; ++i) {
437		CatalogAddOnInfo* info
438			= static_cast<CatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i));
439		delete info;
440	}
441	fCatalogAddOnInfos.MakeEmpty();
442}
443
444
445status_t
446LocaleRosterData::_LoadLocaleSettings()
447{
448	BPath path;
449	BFile file;
450	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
451	if (status == B_OK) {
452		path.Append("Locale settings");
453		status = file.SetTo(path.Path(), B_READ_ONLY);
454	}
455	BMessage settings;
456	if (status == B_OK)
457		status = settings.Unflatten(&file);
458
459	if (status == B_OK) {
460		BFormattingConventions conventions(&settings);
461		fDefaultLocale.SetFormattingConventions(conventions);
462
463		_SetPreferredLanguages(&settings);
464
465		bool preferred;
466		if (settings.FindBool(kTranslateFilesystemField, &preferred) == B_OK)
467			_SetFilesystemTranslationPreferred(preferred);
468
469		return B_OK;
470	}
471
472
473	// Something went wrong (no settings file or invalid BMessage), so we
474	// set everything to default values
475
476	fPreferredLanguages.MakeEmpty();
477	fPreferredLanguages.AddString(kLanguageField, "en");
478	BLanguage defaultLanguage("en_US");
479	fDefaultLocale.SetLanguage(defaultLanguage);
480	BFormattingConventions conventions("en_US");
481	fDefaultLocale.SetFormattingConventions(conventions);
482
483	return status;
484}
485
486
487status_t
488LocaleRosterData::_LoadTimeSettings()
489{
490	BPath path;
491	BFile file;
492	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
493	if (status == B_OK) {
494		path.Append("Time settings");
495		status = file.SetTo(path.Path(), B_READ_ONLY);
496	}
497	BMessage settings;
498	if (status == B_OK)
499		status = settings.Unflatten(&file);
500	if (status == B_OK) {
501		BString timeZoneID;
502		if (settings.FindString(kTimezoneField, &timeZoneID) == B_OK)
503			_SetDefaultTimeZone(BTimeZone(timeZoneID.String()));
504		else
505			_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
506
507		return B_OK;
508	}
509
510	// Something went wrong (no settings file or invalid BMessage), so we
511	// set everything to default values
512	_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
513
514	return status;
515}
516
517
518status_t
519LocaleRosterData::_SaveLocaleSettings()
520{
521	BMessage settings;
522	status_t status = _AddDefaultFormattingConventionsToMessage(&settings);
523	if (status == B_OK)
524		_AddPreferredLanguagesToMessage(&settings);
525	if (status == B_OK)
526		_AddFilesystemTranslationPreferenceToMessage(&settings);
527
528	BPath path;
529	if (status == B_OK)
530		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
531
532	BFile file;
533	if (status == B_OK) {
534		path.Append("Locale settings");
535		status = file.SetTo(path.Path(),
536			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
537	}
538	if (status == B_OK)
539		status = settings.Flatten(&file);
540	if (status == B_OK)
541		status = file.Sync();
542
543	return status;
544}
545
546
547status_t
548LocaleRosterData::_SaveTimeSettings()
549{
550	BMessage settings;
551	status_t status = _AddDefaultTimeZoneToMessage(&settings);
552
553	BPath path;
554	if (status == B_OK)
555		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
556
557	BFile file;
558	if (status == B_OK) {
559		path.Append("Time settings");
560		status = file.SetTo(path.Path(),
561			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
562	}
563	if (status == B_OK)
564		status = settings.Flatten(&file);
565	if (status == B_OK)
566		status = file.Sync();
567
568	return status;
569}
570
571
572status_t
573LocaleRosterData::_SetDefaultFormattingConventions(
574	const BFormattingConventions& newFormattingConventions)
575{
576	fDefaultLocale.SetFormattingConventions(newFormattingConventions);
577
578	UErrorCode icuError = U_ZERO_ERROR;
579	Locale icuLocale = Locale::createCanonical(newFormattingConventions.ID());
580	if (icuLocale.isBogus())
581		return B_ERROR;
582
583	Locale::setDefault(icuLocale, icuError);
584	if (!U_SUCCESS(icuError))
585		return B_ERROR;
586
587	return B_OK;
588}
589
590
591status_t
592LocaleRosterData::_SetDefaultTimeZone(const BTimeZone& newZone)
593{
594	fDefaultTimeZone = newZone;
595
596	TimeZone* timeZone = TimeZone::createTimeZone(newZone.ID().String());
597	if (timeZone == NULL)
598		return B_ERROR;
599	TimeZone::adoptDefault(timeZone);
600
601	return B_OK;
602}
603
604
605status_t
606LocaleRosterData::_SetPreferredLanguages(const BMessage* languages)
607{
608	BString langName;
609	if (languages != NULL
610		&& languages->FindString(kLanguageField, &langName) == B_OK) {
611		fDefaultLocale.SetCollator(BCollator(langName.String()));
612		fDefaultLocale.SetLanguage(BLanguage(langName.String()));
613
614		fPreferredLanguages.RemoveName(kLanguageField);
615		for (int i = 0; languages->FindString(kLanguageField, i, &langName)
616				== B_OK; i++) {
617			fPreferredLanguages.AddString(kLanguageField, langName);
618		}
619	} else {
620		fPreferredLanguages.MakeEmpty();
621		fPreferredLanguages.AddString(kLanguageField, "en");
622		fDefaultLocale.SetCollator(BCollator("en"));
623	}
624
625	return B_OK;
626}
627
628
629void
630LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred)
631{
632	fIsFilesystemTranslationPreferred = preferred;
633}
634
635
636status_t
637LocaleRosterData::_AddDefaultFormattingConventionsToMessage(
638	BMessage* message) const
639{
640	BFormattingConventions conventions;
641	fDefaultLocale.GetFormattingConventions(&conventions);
642
643	return conventions.Archive(message);
644}
645
646
647status_t
648LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const
649{
650	return message->AddString(kTimezoneField, fDefaultTimeZone.ID());
651}
652
653
654status_t
655LocaleRosterData::_AddPreferredLanguagesToMessage(BMessage* message) const
656{
657	status_t status = B_OK;
658
659	BString langName;
660	for (int i = 0; fPreferredLanguages.FindString("language", i,
661			&langName) == B_OK; i++) {
662		status = message->AddString(kLanguageField, langName);
663		if (status != B_OK)
664			break;
665	}
666
667	return status;
668}
669
670
671status_t
672LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage(
673	BMessage* message) const
674{
675	return message->AddBool(kTranslateFilesystemField,
676		fIsFilesystemTranslationPreferred);
677}
678
679
680}	// namespace BPrivate
681