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