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