xref: /haiku/src/kits/locale/LocaleRosterData.cpp (revision 204dee708a999d5a71d0cb9497650ee7cef85d0a)
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 <LocaleRosterData.h>
12 
13 #include <Autolock.h>
14 #include <Catalog.h>
15 #include <Collator.h>
16 #include <Debug.h>
17 #include <DefaultCatalog.h>
18 #include <Directory.h>
19 #include <Entry.h>
20 #include <File.h>
21 #include <FindDirectory.h>
22 #include <FormattingConventions.h>
23 #include <Language.h>
24 #include <Locale.h>
25 #include <Node.h>
26 #include <Path.h>
27 #include <Roster.h>
28 #include <String.h>
29 #include <TimeZone.h>
30 
31 // ICU includes
32 #include <unicode/locid.h>
33 #include <unicode/timezone.h>
34 
35 
36 namespace BPrivate {
37 
38 
39 // #pragma mark - CatalogAddOnInfo
40 
41 
42 CatalogAddOnInfo::CatalogAddOnInfo(const BString& name, const BString& path,
43 	uint8 priority)
44 	:
45 	fInstantiateFunc(NULL),
46 	fCreateFunc(NULL),
47 	fLanguagesFunc(NULL),
48 	fName(name),
49 	fPath(path),
50 	fAddOnImage(B_NO_INIT),
51 	fPriority(priority),
52 	fIsEmbedded(path.Length()==0)
53 {
54 }
55 
56 
57 CatalogAddOnInfo::~CatalogAddOnInfo()
58 {
59 	int32 count = fLoadedCatalogs.CountItems();
60 	for (int32 i = 0; i < count; ++i) {
61 		BCatalogData* cat
62 			= static_cast<BCatalogData*>(fLoadedCatalogs.ItemAt(i));
63 		delete cat;
64 	}
65 	fLoadedCatalogs.MakeEmpty();
66 	UnloadIfPossible();
67 }
68 
69 
70 bool
71 CatalogAddOnInfo::MakeSureItsLoaded()
72 {
73 	if (!fIsEmbedded && fAddOnImage < B_OK) {
74 		// add-on has not been loaded yet, so we try to load it:
75 		BString fullAddOnPath(fPath);
76 		fullAddOnPath << "/" << fName;
77 		fAddOnImage = load_add_on(fullAddOnPath.String());
78 		if (fAddOnImage >= B_OK) {
79 			get_image_symbol(fAddOnImage, "instantiate_catalog",
80 				B_SYMBOL_TYPE_TEXT, (void**)&fInstantiateFunc);
81 			get_image_symbol(fAddOnImage, "create_catalog",
82 				B_SYMBOL_TYPE_TEXT, (void**)&fCreateFunc);
83 			get_image_symbol(fAddOnImage, "get_available_languages",
84 				B_SYMBOL_TYPE_TEXT, (void**)&fLanguagesFunc);
85 		} else
86 			return false;
87 	} else if (fIsEmbedded) {
88 		// The built-in catalog still has to provide this function
89 		fLanguagesFunc = default_catalog_get_available_languages;
90 	}
91 	return true;
92 }
93 
94 
95 void
96 CatalogAddOnInfo::UnloadIfPossible()
97 {
98 	if (!fIsEmbedded && fLoadedCatalogs.IsEmpty()) {
99 		unload_add_on(fAddOnImage);
100 		fAddOnImage = B_NO_INIT;
101 		fInstantiateFunc = NULL;
102 		fCreateFunc = NULL;
103 		fLanguagesFunc = NULL;
104 	}
105 }
106 
107 
108 // #pragma mark - LocaleRosterData
109 
110 
111 namespace {
112 
113 
114 static const char* kPriorityAttr = "ADDON:priority";
115 
116 static const char* kLanguageField = "language";
117 static const char* kTimezoneField = "timezone";
118 static const char* kTranslateFilesystemField = "filesys";
119 
120 
121 }	// anonymous namespace
122 
123 
124 LocaleRosterData::LocaleRosterData(const BLanguage& language,
125 	const BFormattingConventions& conventions)
126 	:
127 	fLock("LocaleRosterData"),
128 	fDefaultLocale(&language, &conventions),
129 	fIsFilesystemTranslationPreferred(false),
130 	fAreResourcesLoaded(false)
131 {
132 	fInitStatus = _Initialize();
133 }
134 
135 
136 LocaleRosterData::~LocaleRosterData()
137 {
138 	BAutolock lock(fLock);
139 
140 	_CleanupCatalogAddOns();
141 }
142 
143 
144 status_t
145 LocaleRosterData::InitCheck() const
146 {
147 	return fAreResourcesLoaded ? B_OK : B_NO_INIT;
148 }
149 
150 
151 status_t
152 LocaleRosterData::Refresh()
153 {
154 	BAutolock lock(fLock);
155 	if (!lock.IsLocked())
156 		return B_ERROR;
157 
158 	_LoadLocaleSettings();
159 	_LoadTimeSettings();
160 
161 	return B_OK;
162 }
163 
164 
165 int
166 LocaleRosterData::CompareInfos(const void* left, const void* right)
167 {
168 	return ((CatalogAddOnInfo*)right)->fPriority
169 		- ((CatalogAddOnInfo*)left)->fPriority;
170 }
171 
172 
173 status_t
174 LocaleRosterData::SetDefaultFormattingConventions(
175 	const BFormattingConventions& newFormattingConventions)
176 {
177 	status_t status = B_OK;
178 
179 	BAutolock lock(fLock);
180 	if (!lock.IsLocked())
181 		return B_ERROR;
182 
183 	status = _SetDefaultFormattingConventions(newFormattingConventions);
184 
185 	if (status == B_OK)
186 		status = _SaveLocaleSettings();
187 
188 	if (status == B_OK) {
189 		BMessage updateMessage(B_LOCALE_CHANGED);
190 		status = _AddDefaultFormattingConventionsToMessage(&updateMessage);
191 		if (status == B_OK)
192 			status = be_roster->Broadcast(&updateMessage);
193 	}
194 
195 	return status;
196 }
197 
198 
199 status_t
200 LocaleRosterData::SetDefaultTimeZone(const BTimeZone& newZone)
201 {
202 	status_t status = B_OK;
203 
204 	BAutolock lock(fLock);
205 	if (!lock.IsLocked())
206 		return B_ERROR;
207 
208 	status = _SetDefaultTimeZone(newZone);
209 
210 	if (status == B_OK)
211 		status = _SaveTimeSettings();
212 
213 	if (status == B_OK) {
214 		BMessage updateMessage(B_LOCALE_CHANGED);
215 		status = _AddDefaultTimeZoneToMessage(&updateMessage);
216 		if (status == B_OK)
217 			status = be_roster->Broadcast(&updateMessage);
218 	}
219 
220 	return status;
221 }
222 
223 
224 status_t
225 LocaleRosterData::SetPreferredLanguages(const BMessage* languages)
226 {
227 	status_t status = B_OK;
228 
229 	BAutolock lock(fLock);
230 	if (!lock.IsLocked())
231 		return B_ERROR;
232 
233 	status = _SetPreferredLanguages(languages);
234 
235 	if (status == B_OK)
236 		status = _SaveLocaleSettings();
237 
238 	if (status == B_OK) {
239 		BMessage updateMessage(B_LOCALE_CHANGED);
240 		status = _AddPreferredLanguagesToMessage(&updateMessage);
241 		if (status == B_OK)
242 			status = be_roster->Broadcast(&updateMessage);
243 	}
244 
245 	return status;
246 }
247 
248 
249 status_t
250 LocaleRosterData::SetFilesystemTranslationPreferred(bool preferred)
251 {
252 	BAutolock lock(fLock);
253 	if (!lock.IsLocked())
254 		return B_ERROR;
255 
256 	_SetFilesystemTranslationPreferred(preferred);
257 
258 	status_t status = _SaveLocaleSettings();
259 
260 	if (status == B_OK) {
261 		BMessage updateMessage(B_LOCALE_CHANGED);
262 		status = _AddFilesystemTranslationPreferenceToMessage(&updateMessage);
263 		if (status == B_OK)
264 			status = be_roster->Broadcast(&updateMessage);
265 	}
266 
267 	return status;
268 }
269 
270 
271 status_t
272 LocaleRosterData::GetResources(BResources** resources)
273 {
274 	if (resources == NULL)
275 		return B_BAD_VALUE;
276 
277 	if (!fAreResourcesLoaded) {
278 		status_t result
279 			= fResources.SetToImage((const void*)&BLocaleRoster::Default);
280 		if (result != B_OK)
281 			return result;
282 
283 		result = fResources.PreloadResourceType();
284 		if (result != B_OK)
285 			return result;
286 
287 		fAreResourcesLoaded = true;
288 	}
289 
290 	*resources = &fResources;
291 	return B_OK;
292 }
293 
294 
295 status_t
296 LocaleRosterData::_Initialize()
297 {
298 	status_t result = _InitializeCatalogAddOns();
299 	if (result != B_OK)
300 		return result;
301 
302 	if ((result = Refresh()) != B_OK)
303 		return result;
304 
305 	fInitStatus = B_OK;
306 	return B_OK;
307 }
308 
309 
310 /*
311 iterate over add-on-folders and collect information about each
312 catalog-add-ons (types of catalogs) into fCatalogAddOnInfos.
313 */
314 status_t
315 LocaleRosterData::_InitializeCatalogAddOns()
316 {
317 	BAutolock lock(fLock);
318 	if (!lock.IsLocked())
319 		return B_ERROR;
320 
321 	// add info about embedded default catalog:
322 	CatalogAddOnInfo* defaultCatalogAddOnInfo
323 		= new(std::nothrow) CatalogAddOnInfo("Default", "",
324 			DefaultCatalog::kDefaultCatalogAddOnPriority);
325 	if (!defaultCatalogAddOnInfo)
326 		return B_NO_MEMORY;
327 
328 	defaultCatalogAddOnInfo->fInstantiateFunc = DefaultCatalog::Instantiate;
329 	defaultCatalogAddOnInfo->fCreateFunc = DefaultCatalog::Create;
330 	fCatalogAddOnInfos.AddItem((void*)defaultCatalogAddOnInfo);
331 
332 	directory_which folders[] = {
333 		B_USER_ADDONS_DIRECTORY,
334 		B_COMMON_ADDONS_DIRECTORY,
335 		B_SYSTEM_ADDONS_DIRECTORY,
336 	};
337 	BPath addOnPath;
338 	BDirectory addOnFolder;
339 	char buf[4096];
340 	status_t err;
341 	for (uint32 f = 0; f < sizeof(folders) / sizeof(directory_which); ++f) {
342 		find_directory(folders[f], &addOnPath);
343 		BString addOnFolderName(addOnPath.Path());
344 		addOnFolderName << "/locale/catalogs";
345 
346 		system_info info;
347 		if (get_system_info(&info) == B_OK
348 				&& (info.abi & B_HAIKU_ABI_MAJOR)
349 				!= (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR)) {
350 			switch (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR) {
351 				case B_HAIKU_ABI_GCC_2:
352 					addOnFolderName << "/gcc2";
353 					break;
354 				case B_HAIKU_ABI_GCC_4:
355 					addOnFolderName << "/gcc4";
356 					break;
357 			}
358 		}
359 
360 
361 		err = addOnFolder.SetTo(addOnFolderName.String());
362 		if (err != B_OK)
363 			continue;
364 
365 		// scan through all the folder's entries for catalog add-ons:
366 		int32 count;
367 		int8 priority;
368 		entry_ref eref;
369 		BNode node;
370 		BEntry entry;
371 		dirent* dent;
372 		while ((count = addOnFolder.GetNextDirents((dirent*)buf, 4096)) > 0) {
373 			dent = (dirent*)buf;
374 			while (count-- > 0) {
375 				if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")
376 						&& strcmp(dent->d_name, "gcc2")
377 						&& strcmp(dent->d_name, "gcc4")) {
378 					// we have found (what should be) a catalog-add-on:
379 					eref.device = dent->d_pdev;
380 					eref.directory = dent->d_pino;
381 					eref.set_name(dent->d_name);
382 					entry.SetTo(&eref, true);
383 						// traverse through any links to get to the real thang!
384 					node.SetTo(&entry);
385 					priority = -1;
386 					if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0,
387 						&priority, sizeof(int8)) <= 0) {
388 						// add-on has no priority-attribute yet, so we load it
389 						// to fetch the priority from the corresponding
390 						// symbol...
391 						BString fullAddOnPath(addOnFolderName);
392 						fullAddOnPath << "/" << dent->d_name;
393 						image_id image = load_add_on(fullAddOnPath.String());
394 						if (image >= B_OK) {
395 							uint8* prioPtr;
396 							if (get_image_symbol(image, "gCatalogAddOnPriority",
397 								B_SYMBOL_TYPE_DATA,
398 								(void**)&prioPtr) == B_OK) {
399 								priority = *prioPtr;
400 								node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0,
401 									&priority, sizeof(int8));
402 							}
403 							unload_add_on(image);
404 						}
405 					}
406 
407 					if (priority >= 0) {
408 						// add-ons with priority < 0 will be ignored
409 						CatalogAddOnInfo* addOnInfo
410 							= new(std::nothrow) CatalogAddOnInfo(dent->d_name,
411 								addOnFolderName, priority);
412 						if (addOnInfo)
413 							fCatalogAddOnInfos.AddItem((void*)addOnInfo);
414 					}
415 				}
416 				// Bump the dirent-pointer by length of the dirent just handled:
417 				dent = (dirent*)((char*)dent + dent->d_reclen);
418 			}
419 		}
420 	}
421 	fCatalogAddOnInfos.SortItems(CompareInfos);
422 
423 	return B_OK;
424 }
425 
426 
427 /*
428  * unloads all catalog-add-ons (which will throw away all loaded catalogs, too)
429  */
430 void
431 LocaleRosterData::_CleanupCatalogAddOns()
432 {
433 	BAutolock lock(fLock);
434 	if (!lock.IsLocked())
435 		return;
436 
437 	int32 count = fCatalogAddOnInfos.CountItems();
438 	for (int32 i = 0; i<count; ++i) {
439 		CatalogAddOnInfo* info
440 			= static_cast<CatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i));
441 		delete info;
442 	}
443 	fCatalogAddOnInfos.MakeEmpty();
444 }
445 
446 
447 status_t
448 LocaleRosterData::_LoadLocaleSettings()
449 {
450 	BPath path;
451 	BFile file;
452 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
453 	if (status == B_OK) {
454 		path.Append("Locale settings");
455 		status = file.SetTo(path.Path(), B_READ_ONLY);
456 	}
457 	BMessage settings;
458 	if (status == B_OK)
459 		status = settings.Unflatten(&file);
460 
461 	if (status == B_OK) {
462 		BFormattingConventions conventions(&settings);
463 		fDefaultLocale.SetFormattingConventions(conventions);
464 
465 		_SetPreferredLanguages(&settings);
466 
467 		bool preferred;
468 		if (settings.FindBool(kTranslateFilesystemField, &preferred) == B_OK)
469 			_SetFilesystemTranslationPreferred(preferred);
470 
471 		return B_OK;
472 	}
473 
474 
475 	// Something went wrong (no settings file or invalid BMessage), so we
476 	// set everything to default values
477 
478 	fPreferredLanguages.MakeEmpty();
479 	fPreferredLanguages.AddString(kLanguageField, "en");
480 	BLanguage defaultLanguage("en_US");
481 	fDefaultLocale.SetLanguage(defaultLanguage);
482 	BFormattingConventions conventions("en_US");
483 	fDefaultLocale.SetFormattingConventions(conventions);
484 
485 	return status;
486 }
487 
488 
489 status_t
490 LocaleRosterData::_LoadTimeSettings()
491 {
492 	BPath path;
493 	BFile file;
494 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
495 	if (status == B_OK) {
496 		path.Append("Time settings");
497 		status = file.SetTo(path.Path(), B_READ_ONLY);
498 	}
499 	BMessage settings;
500 	if (status == B_OK)
501 		status = settings.Unflatten(&file);
502 	if (status == B_OK) {
503 		BString timeZoneID;
504 		if (settings.FindString(kTimezoneField, &timeZoneID) == B_OK)
505 			_SetDefaultTimeZone(BTimeZone(timeZoneID.String()));
506 		else
507 			_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
508 
509 		return B_OK;
510 	}
511 
512 	// Something went wrong (no settings file or invalid BMessage), so we
513 	// set everything to default values
514 	_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
515 
516 	return status;
517 }
518 
519 
520 status_t
521 LocaleRosterData::_SaveLocaleSettings()
522 {
523 	BMessage settings;
524 	status_t status = _AddDefaultFormattingConventionsToMessage(&settings);
525 	if (status == B_OK)
526 		_AddPreferredLanguagesToMessage(&settings);
527 	if (status == B_OK)
528 		_AddFilesystemTranslationPreferenceToMessage(&settings);
529 
530 	BPath path;
531 	if (status == B_OK)
532 		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
533 
534 	BFile file;
535 	if (status == B_OK) {
536 		path.Append("Locale settings");
537 		status = file.SetTo(path.Path(),
538 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
539 	}
540 	if (status == B_OK)
541 		status = settings.Flatten(&file);
542 	if (status == B_OK)
543 		status = file.Sync();
544 
545 	return status;
546 }
547 
548 
549 status_t
550 LocaleRosterData::_SaveTimeSettings()
551 {
552 	BMessage settings;
553 	status_t status = _AddDefaultTimeZoneToMessage(&settings);
554 
555 	BPath path;
556 	if (status == B_OK)
557 		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
558 
559 	BFile file;
560 	if (status == B_OK) {
561 		path.Append("Time settings");
562 		status = file.SetTo(path.Path(),
563 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
564 	}
565 	if (status == B_OK)
566 		status = settings.Flatten(&file);
567 	if (status == B_OK)
568 		status = file.Sync();
569 
570 	return status;
571 }
572 
573 
574 status_t
575 LocaleRosterData::_SetDefaultFormattingConventions(
576 	const BFormattingConventions& newFormattingConventions)
577 {
578 	fDefaultLocale.SetFormattingConventions(newFormattingConventions);
579 
580 	UErrorCode icuError = U_ZERO_ERROR;
581 	Locale icuLocale = Locale::createCanonical(newFormattingConventions.ID());
582 	if (icuLocale.isBogus())
583 		return B_ERROR;
584 
585 	Locale::setDefault(icuLocale, icuError);
586 	if (!U_SUCCESS(icuError))
587 		return B_ERROR;
588 
589 	return B_OK;
590 }
591 
592 
593 status_t
594 LocaleRosterData::_SetDefaultTimeZone(const BTimeZone& newZone)
595 {
596 	fDefaultTimeZone = newZone;
597 
598 	TimeZone* timeZone = TimeZone::createTimeZone(newZone.ID().String());
599 	if (timeZone == NULL)
600 		return B_ERROR;
601 	TimeZone::adoptDefault(timeZone);
602 
603 	return B_OK;
604 }
605 
606 
607 status_t
608 LocaleRosterData::_SetPreferredLanguages(const BMessage* languages)
609 {
610 	BString langName;
611 	if (languages != NULL
612 		&& languages->FindString(kLanguageField, &langName) == B_OK) {
613 		fDefaultLocale.SetCollator(BCollator(langName.String()));
614 		fDefaultLocale.SetLanguage(BLanguage(langName.String()));
615 
616 		fPreferredLanguages.RemoveName(kLanguageField);
617 		for (int i = 0; languages->FindString(kLanguageField, i, &langName)
618 				== B_OK; i++) {
619 			fPreferredLanguages.AddString(kLanguageField, langName);
620 		}
621 	} else {
622 		fPreferredLanguages.MakeEmpty();
623 		fPreferredLanguages.AddString(kLanguageField, "en");
624 		fDefaultLocale.SetCollator(BCollator("en"));
625 	}
626 
627 	return B_OK;
628 }
629 
630 
631 void
632 LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred)
633 {
634 	fIsFilesystemTranslationPreferred = preferred;
635 }
636 
637 
638 status_t
639 LocaleRosterData::_AddDefaultFormattingConventionsToMessage(
640 	BMessage* message) const
641 {
642 	BFormattingConventions conventions;
643 	fDefaultLocale.GetFormattingConventions(&conventions);
644 
645 	return conventions.Archive(message);
646 }
647 
648 
649 status_t
650 LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const
651 {
652 	return message->AddString(kTimezoneField, fDefaultTimeZone.ID());
653 }
654 
655 
656 status_t
657 LocaleRosterData::_AddPreferredLanguagesToMessage(BMessage* message) const
658 {
659 	status_t status = B_OK;
660 
661 	BString langName;
662 	for (int i = 0; fPreferredLanguages.FindString("language", i,
663 			&langName) == B_OK; i++) {
664 		status = message->AddString(kLanguageField, langName);
665 		if (status != B_OK)
666 			break;
667 	}
668 
669 	return status;
670 }
671 
672 
673 status_t
674 LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage(
675 	BMessage* message) const
676 {
677 	return message->AddBool(kTranslateFilesystemField,
678 		fIsFilesystemTranslationPreferred);
679 }
680 
681 
682 }	// namespace BPrivate
683