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