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