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