xref: /haiku/src/kits/locale/LocaleRosterData.cpp (revision e03da2bd84c4d74ab127d5f8e3297a8214117e10)
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 
39 U_NAMESPACE_USE
40 
41 
42 namespace BPrivate {
43 
44 
45 // #pragma mark - CatalogAddOnInfo
46 
47 
CatalogAddOnInfo(const BString & name,const BString & path,uint8 priority)48 CatalogAddOnInfo::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 
~CatalogAddOnInfo()63 CatalogAddOnInfo::~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 
76 bool
MakeSureItsLoaded()77 CatalogAddOnInfo::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 
101 void
UnloadIfPossible()102 CatalogAddOnInfo::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 
117 namespace {
118 
119 
120 static const char* kPriorityAttr = "ADDON:priority";
121 
122 static const char* kLanguageField = "language";
123 static const char* kTimezoneField = "timezone";
124 static const char* kTranslateFilesystemField = "filesys";
125 
126 
127 }	// anonymous namespace
128 
129 
LocaleRosterData(const BLanguage & language,const BFormattingConventions & conventions)130 LocaleRosterData::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 
~LocaleRosterData()142 LocaleRosterData::~LocaleRosterData()
143 {
144 	BAutolock lock(fLock);
145 
146 	_CleanupCatalogAddOns();
147 }
148 
149 
150 status_t
InitCheck() const151 LocaleRosterData::InitCheck() const
152 {
153 	return fAreResourcesLoaded ? B_OK : B_NO_INIT;
154 }
155 
156 
157 status_t
Refresh()158 LocaleRosterData::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 
171 int
CompareInfos(const void * left,const void * right)172 LocaleRosterData::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 
183 status_t
SetDefaultFormattingConventions(const BFormattingConventions & newFormattingConventions)184 LocaleRosterData::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 
209 status_t
SetDefaultTimeZone(const BTimeZone & newZone)210 LocaleRosterData::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 
234 status_t
SetPreferredLanguages(const BMessage * languages)235 LocaleRosterData::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 
259 status_t
SetFilesystemTranslationPreferred(bool preferred)260 LocaleRosterData::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 
281 status_t
GetResources(BResources ** resources)282 LocaleRosterData::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 
305 status_t
_Initialize()306 LocaleRosterData::_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 /*
321 iterate over add-on-folders and collect information about each
322 catalog-add-ons (types of catalogs) into fCatalogAddOnInfos.
323 */
324 status_t
_InitializeCatalogAddOns()325 LocaleRosterData::_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  */
428 void
_CleanupCatalogAddOns()429 LocaleRosterData::_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 
445 status_t
_LoadLocaleSettings()446 LocaleRosterData::_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 
487 status_t
_LoadTimeSettings()488 LocaleRosterData::_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 
518 status_t
_SaveLocaleSettings()519 LocaleRosterData::_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 
547 status_t
_SaveTimeSettings()548 LocaleRosterData::_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 
572 status_t
_SetDefaultFormattingConventions(const BFormattingConventions & newFormattingConventions)573 LocaleRosterData::_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 
591 status_t
_SetDefaultTimeZone(const BTimeZone & newZone)592 LocaleRosterData::_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 
605 status_t
_SetPreferredLanguages(const BMessage * languages)606 LocaleRosterData::_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 
629 void
_SetFilesystemTranslationPreferred(bool preferred)630 LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred)
631 {
632 	fIsFilesystemTranslationPreferred = preferred;
633 }
634 
635 
636 status_t
_AddDefaultFormattingConventionsToMessage(BMessage * message) const637 LocaleRosterData::_AddDefaultFormattingConventionsToMessage(
638 	BMessage* message) const
639 {
640 	BFormattingConventions conventions;
641 	fDefaultLocale.GetFormattingConventions(&conventions);
642 
643 	return conventions.Archive(message);
644 }
645 
646 
647 status_t
_AddDefaultTimeZoneToMessage(BMessage * message) const648 LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const
649 {
650 	return message->AddString(kTimezoneField, fDefaultTimeZone.ID());
651 }
652 
653 
654 status_t
_AddPreferredLanguagesToMessage(BMessage * message) const655 LocaleRosterData::_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 
671 status_t
_AddFilesystemTranslationPreferenceToMessage(BMessage * message) const672 LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage(
673 	BMessage* message) const
674 {
675 	return message->AddBool(kTranslateFilesystemField,
676 		fIsFilesystemTranslationPreferred);
677 }
678 
679 
680 }	// namespace BPrivate
681