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