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