xref: /haiku/src/kits/locale/MutableLocaleRoster.cpp (revision 56187df6ebd9d1fb16bf954fce8df0999e9e3048)
1 /*
2  * Copyright 2003-2010, 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 <MutableLocaleRoster.h>
12 
13 #include <set>
14 
15 #include <syslog.h>
16 
17 #include <AppFileInfo.h>
18 #include <Autolock.h>
19 #include <Catalog.h>
20 #include <Collator.h>
21 #include <Country.h>
22 #include <DefaultCatalog.h>
23 #include <Directory.h>
24 #include <Entry.h>
25 #include <File.h>
26 #include <FindDirectory.h>
27 #include <Language.h>
28 #include <Locale.h>
29 #include <Node.h>
30 #include <Path.h>
31 #include <Roster.h>
32 #include <String.h>
33 #include <TimeZone.h>
34 
35 #include <ICUWrapper.h>
36 
37 // ICU includes
38 #include <unicode/locid.h>
39 #include <unicode/timezone.h>
40 
41 
42 namespace BPrivate {
43 
44 
45 // #pragma mark - CatalogAddOnInfo
46 
47 
48 CatalogAddOnInfo::CatalogAddOnInfo(const BString& name, const BString& path,
49 	uint8 priority)
50 	:
51 	fInstantiateFunc(NULL),
52 	fInstantiateEmbeddedFunc(NULL),
53 	fCreateFunc(NULL),
54 	fLanguagesFunc(NULL),
55 	fName(name),
56 	fPath(path),
57 	fAddOnImage(B_NO_INIT),
58 	fPriority(priority),
59 	fIsEmbedded(path.Length()==0)
60 {
61 }
62 
63 
64 CatalogAddOnInfo::~CatalogAddOnInfo()
65 {
66 	int32 count = fLoadedCatalogs.CountItems();
67 	for (int32 i = 0; i < count; ++i) {
68 		BCatalogAddOn* cat
69 			= static_cast<BCatalogAddOn*>(fLoadedCatalogs.ItemAt(i));
70 		delete cat;
71 	}
72 	fLoadedCatalogs.MakeEmpty();
73 	UnloadIfPossible();
74 }
75 
76 
77 bool
78 CatalogAddOnInfo::MakeSureItsLoaded()
79 {
80 	if (!fIsEmbedded && fAddOnImage < B_OK) {
81 		// add-on has not been loaded yet, so we try to load it:
82 		BString fullAddOnPath(fPath);
83 		fullAddOnPath << "/" << fName;
84 		fAddOnImage = load_add_on(fullAddOnPath.String());
85 		if (fAddOnImage >= B_OK) {
86 			get_image_symbol(fAddOnImage, "instantiate_catalog",
87 				B_SYMBOL_TYPE_TEXT, (void**)&fInstantiateFunc);
88 			get_image_symbol(fAddOnImage, "instantiate_embedded_catalog",
89 				B_SYMBOL_TYPE_TEXT, (void**)&fInstantiateEmbeddedFunc);
90 			get_image_symbol(fAddOnImage, "create_catalog",
91 				B_SYMBOL_TYPE_TEXT, (void**)&fCreateFunc);
92 			get_image_symbol(fAddOnImage, "get_available_languages",
93 				B_SYMBOL_TYPE_TEXT, (void**)&fLanguagesFunc);
94 			log_team(LOG_DEBUG, "catalog-add-on %s has been loaded",
95 				fName.String());
96 		} else {
97 			log_team(LOG_DEBUG, "could not load catalog-add-on %s (%s)",
98 				fName.String(), strerror(fAddOnImage));
99 			return false;
100 		}
101 	} else if (fIsEmbedded) {
102 		// The built-in catalog still has to provide this function
103 		fLanguagesFunc = default_catalog_get_available_languages;
104 	}
105 	return true;
106 }
107 
108 
109 void
110 CatalogAddOnInfo::UnloadIfPossible()
111 {
112 	if (!fIsEmbedded && fLoadedCatalogs.IsEmpty()) {
113 		unload_add_on(fAddOnImage);
114 		fAddOnImage = B_NO_INIT;
115 		fInstantiateFunc = NULL;
116 		fInstantiateEmbeddedFunc = NULL;
117 		fCreateFunc = NULL;
118 		fLanguagesFunc = NULL;
119 //		log_team(LOG_DEBUG, "catalog-add-on %s has been unloaded",
120 //			fName.String());
121 	}
122 }
123 
124 
125 // #pragma mark - RosterData
126 
127 
128 RosterData gRosterData;
129 
130 static const char* kPriorityAttr = "ADDON:priority";
131 
132 
133 RosterData::RosterData()
134 	:
135 	fLock("LocaleRosterData"),
136 	fAreResourcesLoaded(false)
137 {
138 	openlog_team("liblocale.so", LOG_PID, LOG_USER);
139 #ifndef DEBUG
140 	setlogmask_team(LOG_UPTO(LOG_WARNING));
141 #endif
142 
143 	InitializeCatalogAddOns();
144 
145 	Refresh();
146 }
147 
148 
149 RosterData::~RosterData()
150 {
151 	BAutolock lock(fLock);
152 
153 	CleanupCatalogAddOns();
154 	closelog();
155 }
156 
157 
158 int
159 RosterData::CompareInfos(const void* left, const void* right)
160 {
161 	return ((CatalogAddOnInfo*)right)->fPriority
162 		- ((CatalogAddOnInfo*)left)->fPriority;
163 }
164 
165 
166 status_t
167 RosterData::Refresh()
168 {
169 	BAutolock lock(fLock);
170 	if (!lock.IsLocked())
171 		return B_ERROR;
172 
173 	_LoadLocaleSettings();
174 	_LoadTimeSettings();
175 
176 	return B_OK;
177 }
178 
179 /*
180 iterate over add-on-folders and collect information about each
181 catalog-add-ons (types of catalogs) into fCatalogAddOnInfos.
182 */
183 void
184 RosterData::InitializeCatalogAddOns()
185 {
186 	BAutolock lock(fLock);
187 	if (!lock.IsLocked())
188 		return;
189 
190 	// add info about embedded default catalog:
191 	CatalogAddOnInfo* defaultCatalogAddOnInfo
192 		= new(std::nothrow) CatalogAddOnInfo("Default", "",
193 			DefaultCatalog::kDefaultCatalogAddOnPriority);
194 	if (!defaultCatalogAddOnInfo)
195 		return;
196 
197 	defaultCatalogAddOnInfo->fInstantiateFunc = DefaultCatalog::Instantiate;
198 	defaultCatalogAddOnInfo->fInstantiateEmbeddedFunc
199 		= DefaultCatalog::InstantiateEmbedded;
200 	defaultCatalogAddOnInfo->fCreateFunc = DefaultCatalog::Create;
201 	fCatalogAddOnInfos.AddItem((void*)defaultCatalogAddOnInfo);
202 
203 	directory_which folders[] = {
204 		B_COMMON_ADDONS_DIRECTORY,
205 		B_SYSTEM_ADDONS_DIRECTORY,
206 		static_cast<directory_which>(-1)
207 	};
208 	BPath addOnPath;
209 	BDirectory addOnFolder;
210 	char buf[4096];
211 	status_t err;
212 	for (int f = 0; folders[f]>=0; ++f) {
213 		find_directory(folders[f], &addOnPath);
214 		BString addOnFolderName(addOnPath.Path());
215 		addOnFolderName << "/locale/catalogs";
216 
217 		system_info info;
218 		if (get_system_info(&info) == B_OK
219 				&& (info.abi & B_HAIKU_ABI_MAJOR)
220 				!= (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR)) {
221 			switch (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR) {
222 				case B_HAIKU_ABI_GCC_2:
223 					addOnFolderName << "/gcc2";
224 					break;
225 				case B_HAIKU_ABI_GCC_4:
226 					addOnFolderName << "/gcc4";
227 					break;
228 			}
229 		}
230 
231 
232 		err = addOnFolder.SetTo(addOnFolderName.String());
233 		if (err != B_OK)
234 			continue;
235 
236 		// scan through all the folder's entries for catalog add-ons:
237 		int32 count;
238 		int8 priority;
239 		entry_ref eref;
240 		BNode node;
241 		BEntry entry;
242 		dirent* dent;
243 		while ((count = addOnFolder.GetNextDirents((dirent*)buf, 4096)) > 0) {
244 			dent = (dirent*)buf;
245 			while (count-- > 0) {
246 				if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")
247 						&& strcmp(dent->d_name, "gcc2")
248 						&& strcmp(dent->d_name, "gcc4")) {
249 					// we have found (what should be) a catalog-add-on:
250 					eref.device = dent->d_pdev;
251 					eref.directory = dent->d_pino;
252 					eref.set_name(dent->d_name);
253 					entry.SetTo(&eref, true);
254 						// traverse through any links to get to the real thang!
255 					node.SetTo(&entry);
256 					priority = -1;
257 					if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0,
258 						&priority, sizeof(int8)) <= 0) {
259 						// add-on has no priority-attribute yet, so we load it
260 						// to fetch the priority from the corresponding
261 						// symbol...
262 						BString fullAddOnPath(addOnFolderName);
263 						fullAddOnPath << "/" << dent->d_name;
264 						image_id image = load_add_on(fullAddOnPath.String());
265 						if (image >= B_OK) {
266 							uint8* prioPtr;
267 							if (get_image_symbol(image, "gCatalogAddOnPriority",
268 								B_SYMBOL_TYPE_DATA,
269 								(void**)&prioPtr) == B_OK) {
270 								priority = *prioPtr;
271 								node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0,
272 									&priority, sizeof(int8));
273 							} else {
274 								log_team(LOG_ERR,
275 									"couldn't get priority for add-on %s\n",
276 									fullAddOnPath.String());
277 							}
278 							unload_add_on(image);
279 						} else {
280 							log_team(LOG_ERR,
281 								"couldn't load add-on %s, error: %s\n",
282 								fullAddOnPath.String(), strerror(image));
283 						}
284 					}
285 
286 					if (priority >= 0) {
287 						// add-ons with priority < 0 will be ignored
288 						CatalogAddOnInfo* addOnInfo
289 							= new(std::nothrow) CatalogAddOnInfo(dent->d_name,
290 								addOnFolderName, priority);
291 						if (addOnInfo)
292 							fCatalogAddOnInfos.AddItem((void*)addOnInfo);
293 					}
294 				}
295 				// Bump the dirent-pointer by length of the dirent just handled:
296 				dent = (dirent*)((char*)dent + dent->d_reclen);
297 			}
298 		}
299 	}
300 	fCatalogAddOnInfos.SortItems(CompareInfos);
301 }
302 
303 
304 /*
305  * unloads all catalog-add-ons (which will throw away all loaded catalogs, too)
306  */
307 void
308 RosterData::CleanupCatalogAddOns()
309 {
310 	BAutolock lock(fLock);
311 	if (!lock.IsLocked())
312 		return;
313 
314 	int32 count = fCatalogAddOnInfos.CountItems();
315 	for (int32 i = 0; i<count; ++i) {
316 		CatalogAddOnInfo* info
317 			= static_cast<CatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i));
318 		delete info;
319 	}
320 	fCatalogAddOnInfos.MakeEmpty();
321 }
322 
323 
324 status_t
325 RosterData::SetDefaultCountry(const BCountry& newCountry)
326 {
327 	status_t status = B_OK;
328 
329 	BAutolock lock(fLock);
330 	if (!lock.IsLocked())
331 		return B_ERROR;
332 
333 	status = _SetDefaultCountry(newCountry);
334 
335 	if (status == B_OK)
336 		status = _SaveLocaleSettings();
337 
338 	if (status == B_OK) {
339 		BMessage updateMessage(B_LOCALE_CHANGED);
340 		status = _AddDefaultCountryToMessage(&updateMessage);
341 		if (status == B_OK)
342 			status = be_roster->Broadcast(&updateMessage);
343 	}
344 
345 	return status;
346 }
347 
348 
349 status_t
350 RosterData::SetDefaultLocale(const BLocale& newLocale)
351 {
352 	status_t status = B_OK;
353 
354 	BAutolock lock(fLock);
355 	if (!lock.IsLocked())
356 		return B_ERROR;
357 
358 	status = _SetDefaultLocale(newLocale);
359 
360 	if (status == B_OK)
361 		status = _SaveLocaleSettings();
362 
363 	if (status == B_OK) {
364 		BMessage updateMessage(B_LOCALE_CHANGED);
365 		status = _AddDefaultCountryToMessage(&updateMessage);
366 			// TODO : should be differenciated from country
367 		if (status == B_OK)
368 			status = be_roster->Broadcast(&updateMessage);
369 	}
370 
371 	return status;
372 }
373 
374 
375 status_t
376 RosterData::SetDefaultTimeZone(const BTimeZone& newZone)
377 {
378 	status_t status = B_OK;
379 
380 	BAutolock lock(fLock);
381 	if (!lock.IsLocked())
382 		return B_ERROR;
383 
384 	status = _SetDefaultTimeZone(newZone);
385 
386 	if (status == B_OK)
387 		status = _SaveTimeSettings();
388 
389 	if (status == B_OK) {
390 		BMessage updateMessage(B_LOCALE_CHANGED);
391 		status = _AddDefaultTimeZoneToMessage(&updateMessage);
392 		if (status == B_OK)
393 			status = be_roster->Broadcast(&updateMessage);
394 	}
395 
396 	return status;
397 }
398 
399 
400 status_t
401 RosterData::SetPreferredLanguages(const BMessage* languages)
402 {
403 	status_t status = B_OK;
404 
405 	BAutolock lock(fLock);
406 	if (!lock.IsLocked())
407 		return B_ERROR;
408 
409 	status = _SetPreferredLanguages(languages);
410 
411 	if (status == B_OK)
412 		status = _SaveLocaleSettings();
413 
414 	if (status == B_OK) {
415 		BMessage updateMessage(B_LOCALE_CHANGED);
416 		status = _AddPreferredLanguagesToMessage(&updateMessage);
417 		if (status == B_OK)
418 			status = be_roster->Broadcast(&updateMessage);
419 	}
420 
421 	return status;
422 }
423 
424 
425 status_t
426 RosterData::_LoadLocaleSettings()
427 {
428 	BPath path;
429 	BFile file;
430 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
431 	if (status == B_OK) {
432 		path.Append("Locale settings");
433 		status = file.SetTo(path.Path(), B_READ_ONLY);
434 	}
435 	BMessage settings;
436 	if (status == B_OK)
437 		status = settings.Unflatten(&file);
438 
439 	if (status == B_OK) {
440 		BString codeName;
441 		BLocale newDefaultLocale;
442 
443 		if (settings.FindString("country", &codeName) == B_OK) {
444 			BCountry country(codeName);
445 			newDefaultLocale = BLocale(NULL, &country);
446 		}
447 
448 		BString timeFormat;
449 		if (settings.FindString("shortTimeFormat", &timeFormat) == B_OK)
450 			newDefaultLocale.SetTimeFormat(timeFormat, false);
451 		if (settings.FindString("longTimeFormat", &timeFormat) == B_OK)
452 			newDefaultLocale.SetTimeFormat(timeFormat, true);
453 
454 		_SetDefaultLocale(newDefaultLocale);
455 
456 		_SetPreferredLanguages(&settings);
457 
458 		return B_OK;
459 	}
460 
461 
462 	// Something went wrong (no settings file or invalid BMessage), so we
463 	// set everything to default values
464 
465 	fPreferredLanguages.MakeEmpty();
466 	fPreferredLanguages.AddString("language", "en");
467 	BLanguage defaultLanguage("en_US");
468 	_SetDefaultLocale(BLocale(&defaultLanguage));
469 
470 	return status;
471 }
472 
473 
474 status_t
475 RosterData::_LoadTimeSettings()
476 {
477 	BPath path;
478 	BFile file;
479 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
480 	if (status == B_OK) {
481 		path.Append("Time settings");
482 		status = file.SetTo(path.Path(), B_READ_ONLY);
483 	}
484 	BMessage settings;
485 	if (status == B_OK)
486 		status = settings.Unflatten(&file);
487 	if (status == B_OK) {
488 		BString timeZoneID;
489 		if (settings.FindString("timezone", &timeZoneID) == B_OK)
490 			_SetDefaultTimeZone(BTimeZone(timeZoneID.String()));
491 		else
492 			_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
493 
494 		return B_OK;
495 	}
496 
497 	// Something went wrong (no settings file or invalid BMessage), so we
498 	// set everything to default values
499 	_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
500 
501 	return status;
502 }
503 
504 
505 status_t
506 RosterData::_SaveLocaleSettings()
507 {
508 	BMessage settings;
509 	status_t status = _AddDefaultCountryToMessage(&settings);
510 	if (status == B_OK)
511 		_AddPreferredLanguagesToMessage(&settings);
512 
513 	BPath path;
514 	if (status == B_OK)
515 		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
516 
517 	BFile file;
518 	if (status == B_OK) {
519 		path.Append("Locale settings");
520 		status = file.SetTo(path.Path(),
521 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
522 	}
523 	if (status == B_OK)
524 		status = settings.Flatten(&file);
525 	if (status == B_OK)
526 		status = file.Sync();
527 
528 	return status;
529 }
530 
531 
532 status_t
533 RosterData::_SaveTimeSettings()
534 {
535 	BMessage settings;
536 	status_t status = _AddDefaultTimeZoneToMessage(&settings);
537 
538 	BPath path;
539 	if (status == B_OK)
540 		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
541 
542 	BFile file;
543 	if (status == B_OK) {
544 		path.Append("Time settings");
545 		status = file.SetTo(path.Path(),
546 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
547 	}
548 	if (status == B_OK)
549 		status = settings.Flatten(&file);
550 	if (status == B_OK)
551 		status = file.Sync();
552 
553 	return status;
554 }
555 
556 
557 status_t
558 RosterData::_SetDefaultCountry(const BCountry& newCountry)
559 {
560 	fDefaultLocale.SetCountry(newCountry);
561 
562 	UErrorCode icuError = U_ZERO_ERROR;
563 	Locale icuLocale = Locale::createCanonical(newCountry.Code());
564 	if (icuLocale.isBogus())
565 		return B_ERROR;
566 
567 	Locale::setDefault(icuLocale, icuError);
568 	if (!U_SUCCESS(icuError))
569 		return B_ERROR;
570 
571 	return B_OK;
572 }
573 
574 
575 status_t
576 RosterData::_SetDefaultLocale(const BLocale& newLocale)
577 {
578 	fDefaultLocale = newLocale;
579 
580 	UErrorCode icuError = U_ZERO_ERROR;
581 	BCountry defaultCountry;
582 	newLocale.GetCountry(&defaultCountry);
583 	Locale icuLocale = Locale::createCanonical(defaultCountry.Code());
584 	if (icuLocale.isBogus())
585 		return B_ERROR;
586 
587 	Locale::setDefault(icuLocale, icuError);
588 	if (!U_SUCCESS(icuError))
589 		return B_ERROR;
590 
591 	return B_OK;
592 }
593 
594 
595 status_t
596 RosterData::_SetDefaultTimeZone(const BTimeZone& newZone)
597 {
598 	fDefaultTimeZone = newZone;
599 
600 	TimeZone* timeZone = TimeZone::createTimeZone(newZone.ID().String());
601 	if (timeZone == NULL)
602 		return B_ERROR;
603 	TimeZone::adoptDefault(timeZone);
604 
605 	return B_OK;
606 }
607 
608 
609 status_t
610 RosterData::_SetPreferredLanguages(const BMessage* languages)
611 {
612 	BString langName;
613 	if (languages != NULL
614 		&& languages->FindString("language", &langName) == B_OK) {
615 		fDefaultLocale.SetCollator(BCollator(langName.String()));
616 		fDefaultLocale.SetLanguage(BLanguage(langName.String()));
617 
618 		fPreferredLanguages.RemoveName("language");
619 		for (int i = 0; languages->FindString("language", i, &langName) == B_OK;
620 			i++) {
621 			fPreferredLanguages.AddString("language", langName);
622 		}
623 	} else {
624 		fPreferredLanguages.MakeEmpty();
625 		fPreferredLanguages.AddString("language", "en");
626 		fDefaultLocale.SetCollator(BCollator("en"));
627 	}
628 
629 	return B_OK;
630 }
631 
632 
633 status_t
634 RosterData::_AddDefaultCountryToMessage(BMessage* message) const
635 {
636 	BCountry defaultCountry;
637 	fDefaultLocale.GetCountry(&defaultCountry);
638 	status_t status = message->AddString("country", defaultCountry.Code());
639 	BString timeFormat;
640 	if (status == B_OK)
641 		status = fDefaultLocale.GetTimeFormat(timeFormat, false);
642 	if (status == B_OK)
643 		status = message->AddString("shortTimeFormat", timeFormat.String());
644 	if (status == B_OK)
645 		status = fDefaultLocale.GetTimeFormat(timeFormat, true);
646 	if (status == B_OK)
647 		status = message->AddString("longTimeFormat", timeFormat.String());
648 
649 	return status;
650 }
651 
652 
653 status_t
654 RosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const
655 {
656 	status_t status = message->AddString("timezone", fDefaultTimeZone.ID());
657 
658 	// add the offset, too, since that is used by clockconfig when setting
659 	// up timezone state during boot
660 	if (status == B_OK)
661 		status = message->AddInt32("offset", fDefaultTimeZone.OffsetFromGMT());
662 
663 	return status;
664 }
665 
666 
667 status_t
668 RosterData::_AddPreferredLanguagesToMessage(BMessage* message) const
669 {
670 	status_t status = B_OK;
671 
672 	BString langName;
673 	for (int i = 0; fPreferredLanguages.FindString("language", i,
674 			&langName) == B_OK; i++) {
675 		status = message->AddString("language", langName);
676 		if (status != B_OK)
677 			break;
678 	}
679 
680 	return status;
681 }
682 
683 
684 // #pragma mark - MutableLocaleRoster
685 
686 
687 static MutableLocaleRoster gLocaleRoster;
688 MutableLocaleRoster* gMutableLocaleRoster = &gLocaleRoster;
689 
690 
691 MutableLocaleRoster::MutableLocaleRoster()
692 {
693 }
694 
695 
696 MutableLocaleRoster::~MutableLocaleRoster()
697 {
698 }
699 
700 
701 status_t
702 MutableLocaleRoster::SetDefaultCountry(const BCountry& newCountry)
703 {
704 	return gRosterData.SetDefaultCountry(newCountry);
705 }
706 
707 
708 status_t
709 MutableLocaleRoster::SetDefaultLocale(const BLocale& newLocale)
710 {
711 	return gRosterData.SetDefaultLocale(newLocale);
712 }
713 
714 
715 status_t
716 MutableLocaleRoster::SetDefaultTimeZone(const BTimeZone& newZone)
717 {
718 	return gRosterData.SetDefaultTimeZone(newZone);
719 }
720 
721 
722 status_t
723 MutableLocaleRoster::SetPreferredLanguages(const BMessage* languages)
724 {
725 	return gRosterData.SetPreferredLanguages(languages);
726 }
727 
728 
729 status_t
730 MutableLocaleRoster::GetDefaultLocale(BLocale* locale) const
731 {
732 	if (!locale)
733 		return B_BAD_VALUE;
734 
735 	BAutolock lock(gRosterData.fLock);
736 	if (!lock.IsLocked())
737 		return B_ERROR;
738 
739 	*locale = gRosterData.fDefaultLocale;
740 
741 	return B_OK;
742 }
743 
744 
745 status_t
746 MutableLocaleRoster::GetSystemCatalog(BCatalogAddOn** catalog) const
747 {
748 	if (!catalog)
749 		return B_BAD_VALUE;
750 	*catalog = LoadCatalog("system");
751 	return B_OK;
752 }
753 
754 
755 /*
756  * creates a new (empty) catalog of the given type (the request is dispatched
757  * to the appropriate add-on).
758  * If the add-on doesn't support catalog-creation or if the creation fails,
759  * NULL is returned, otherwise a pointer to the freshly created catalog.
760  * Any created catalog will be initialized with the given signature and
761  * language-name.
762  */
763 BCatalogAddOn*
764 MutableLocaleRoster::CreateCatalog(const char* type, const char* signature,
765 	const char* language)
766 {
767 	if (!type || !signature || !language)
768 		return NULL;
769 
770 	BAutolock lock(gRosterData.fLock);
771 	if (!lock.IsLocked())
772 		return NULL;
773 
774 	int32 count = gRosterData.fCatalogAddOnInfos.CountItems();
775 	for (int32 i = 0; i < count; ++i) {
776 		CatalogAddOnInfo* info
777 			= (CatalogAddOnInfo*)gRosterData.fCatalogAddOnInfos.ItemAt(i);
778 		if (info->fName.ICompare(type)!=0 || !info->MakeSureItsLoaded()
779 			|| !info->fCreateFunc)
780 			continue;
781 
782 		BCatalogAddOn* catalog = info->fCreateFunc(signature, language);
783 		if (catalog) {
784 			info->fLoadedCatalogs.AddItem(catalog);
785 			info->UnloadIfPossible();
786 			return catalog;
787 		}
788 	}
789 
790 	return NULL;
791 }
792 
793 
794 /*
795  * Loads a catalog for the given signature, language and fingerprint.
796  * The request to load this catalog is dispatched to all add-ons in turn,
797  * until an add-on reports success.
798  * If a catalog depends on another language (as 'english-british' depends
799  * on 'english') the dependant catalogs are automatically loaded, too.
800  * So it is perfectly possible that this method returns a catalog-chain
801  * instead of a single catalog.
802  * NULL is returned if no matching catalog could be found.
803  */
804 BCatalogAddOn*
805 MutableLocaleRoster::LoadCatalog(const char* signature, const char* language,
806 	int32 fingerprint) const
807 {
808 	if (!signature)
809 		return NULL;
810 
811 	BAutolock lock(gRosterData.fLock);
812 	if (!lock.IsLocked())
813 		return NULL;
814 
815 	int32 count = gRosterData.fCatalogAddOnInfos.CountItems();
816 	for (int32 i = 0; i < count; ++i) {
817 		CatalogAddOnInfo* info
818 			= (CatalogAddOnInfo*)gRosterData.fCatalogAddOnInfos.ItemAt(i);
819 
820 		if (!info->MakeSureItsLoaded() || !info->fInstantiateFunc)
821 			continue;
822 		BMessage languages;
823 		if (language)
824 			// try to load catalogs for the given language:
825 			languages.AddString("language", language);
826 		else
827 			// try to load catalogs for one of the preferred languages:
828 			GetPreferredLanguages(&languages);
829 
830 		BCatalogAddOn* catalog = NULL;
831 		const char* lang;
832 		for (int32 l=0; languages.FindString("language", l, &lang)==B_OK; ++l) {
833 			catalog = info->fInstantiateFunc(signature, lang, fingerprint);
834 			if (catalog)
835 				info->fLoadedCatalogs.AddItem(catalog);
836 			// Chain-load catalogs for languages that depend on
837 			// other languages.
838 			// The current implementation uses the filename in order to
839 			// detect dependencies (parenthood) between languages (it
840 			// traverses from "english_british_oxford" to "english_british"
841 			// to "english"):
842 			// TODO: use ICU facilities instead, so we can handle more
843 			// complex things such as fr_FR@euro, or whatever, encodings
844 			// and so on.
845 			int32 pos;
846 			BString langName(lang);
847 			BCatalogAddOn* currCatalog = catalog;
848 			BCatalogAddOn* nextCatalog;
849 			while ((pos = langName.FindLast('_')) >= 0) {
850 				// language is based on parent, so we load that, too:
851 				// (even if the parent catalog was not found)
852 				langName.Truncate(pos);
853 				nextCatalog = info->fInstantiateFunc(signature,
854 					langName.String(), fingerprint);
855 				if (nextCatalog) {
856 					info->fLoadedCatalogs.AddItem(nextCatalog);
857 					if(currCatalog)
858 						currCatalog->SetNext(nextCatalog);
859 					else
860 						catalog = nextCatalog;
861 					currCatalog = nextCatalog;
862 				}
863 			}
864 			return catalog;
865 		}
866 		info->UnloadIfPossible();
867 	}
868 
869 	return NULL;
870 }
871 
872 
873 /*
874  * Loads an embedded catalog from the given entry-ref (which is usually an
875  * app- or add-on-file. The request to load the catalog is dispatched to all
876  * add-ons in turn, until an add-on reports success.
877  * NULL is returned if no embedded catalog could be found.
878  */
879 BCatalogAddOn*
880 MutableLocaleRoster::LoadEmbeddedCatalog(entry_ref* appOrAddOnRef)
881 {
882 	if (!appOrAddOnRef)
883 		return NULL;
884 
885 	BAutolock lock(gRosterData.fLock);
886 	if (!lock.IsLocked())
887 		return NULL;
888 
889 	int32 count = gRosterData.fCatalogAddOnInfos.CountItems();
890 	for (int32 i = 0; i < count; ++i) {
891 		CatalogAddOnInfo* info
892 			= (CatalogAddOnInfo*)gRosterData.fCatalogAddOnInfos.ItemAt(i);
893 
894 		if (!info->MakeSureItsLoaded() || !info->fInstantiateEmbeddedFunc)
895 			continue;
896 
897 		BCatalogAddOn* catalog = NULL;
898 		catalog = info->fInstantiateEmbeddedFunc(appOrAddOnRef);
899 		if (catalog) {
900 			info->fLoadedCatalogs.AddItem(catalog);
901 			return catalog;
902 		}
903 		info->UnloadIfPossible();
904 	}
905 
906 	return NULL;
907 }
908 
909 
910 /*
911  * unloads the given catalog (or rather: catalog-chain).
912  * Every single catalog of the chain will be deleted automatically.
913  * Add-ons that have no more current catalogs are unloaded, too.
914  */
915 status_t
916 MutableLocaleRoster::UnloadCatalog(BCatalogAddOn* catalog)
917 {
918 	if (!catalog)
919 		return B_BAD_VALUE;
920 
921 	BAutolock lock(gRosterData.fLock);
922 	if (!lock.IsLocked())
923 		return B_ERROR;
924 
925 	status_t res = B_ERROR;
926 	BCatalogAddOn* nextCatalog;
927 
928 	while (catalog) {
929 		nextCatalog = catalog->Next();
930 		int32 count = gRosterData.fCatalogAddOnInfos.CountItems();
931 		for (int32 i = 0; i < count; ++i) {
932 			CatalogAddOnInfo* info = static_cast<CatalogAddOnInfo*>(
933 				gRosterData.fCatalogAddOnInfos.ItemAt(i));
934 			if (info->fLoadedCatalogs.HasItem(catalog)) {
935 				info->fLoadedCatalogs.RemoveItem(catalog);
936 				delete catalog;
937 				info->UnloadIfPossible();
938 				res = B_OK;
939 			}
940 		}
941 		catalog = nextCatalog;
942 	}
943 	return res;
944 }
945 
946 
947 }	// namespace BPrivate
948 
949 
950 BLocaleRoster* be_locale_roster = &BPrivate::gLocaleRoster;
951 
952 const BLocale* be_locale = &BPrivate::gRosterData.fDefaultLocale;
953