xref: /haiku/src/kits/locale/LocaleRosterData.cpp (revision 2b76973fa2401f7a5edf68e6470f3d3210cbcff3)
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_NONPACKAGED_ADDONS_DIRECTORY,
334 		B_USER_ADDONS_DIRECTORY,
335 		B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
336 		B_SYSTEM_ADDONS_DIRECTORY,
337 	};
338 	BPath addOnPath;
339 	BDirectory addOnFolder;
340 	char buf[4096];
341 	status_t err;
342 	for (uint32 f = 0; f < sizeof(folders) / sizeof(directory_which); ++f) {
343 		find_directory(folders[f], &addOnPath);
344 		BString addOnFolderName(addOnPath.Path());
345 		addOnFolderName << "/locale/catalogs";
346 
347 		system_info info;
348 		if (get_system_info(&info) == B_OK
349 				&& (info.abi & B_HAIKU_ABI_MAJOR)
350 				!= (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR)) {
351 			switch (B_HAIKU_ABI & B_HAIKU_ABI_MAJOR) {
352 				case B_HAIKU_ABI_GCC_2:
353 					addOnFolderName << "/gcc2";
354 					break;
355 				case B_HAIKU_ABI_GCC_4:
356 					addOnFolderName << "/gcc4";
357 					break;
358 			}
359 		}
360 
361 
362 		err = addOnFolder.SetTo(addOnFolderName.String());
363 		if (err != B_OK)
364 			continue;
365 
366 		// scan through all the folder's entries for catalog add-ons:
367 		int32 count;
368 		int8 priority;
369 		entry_ref eref;
370 		BNode node;
371 		BEntry entry;
372 		dirent* dent;
373 		while ((count = addOnFolder.GetNextDirents((dirent*)buf, 4096)) > 0) {
374 			dent = (dirent*)buf;
375 			while (count-- > 0) {
376 				if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")
377 						&& strcmp(dent->d_name, "gcc2")
378 						&& strcmp(dent->d_name, "gcc4")) {
379 					// we have found (what should be) a catalog-add-on:
380 					eref.device = dent->d_pdev;
381 					eref.directory = dent->d_pino;
382 					eref.set_name(dent->d_name);
383 					entry.SetTo(&eref, true);
384 						// traverse through any links to get to the real thang!
385 					node.SetTo(&entry);
386 					priority = -1;
387 					if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0,
388 						&priority, sizeof(int8)) <= 0) {
389 						// add-on has no priority-attribute yet, so we load it
390 						// to fetch the priority from the corresponding
391 						// symbol...
392 						BString fullAddOnPath(addOnFolderName);
393 						fullAddOnPath << "/" << dent->d_name;
394 						image_id image = load_add_on(fullAddOnPath.String());
395 						if (image >= B_OK) {
396 							uint8* prioPtr;
397 							if (get_image_symbol(image, "gCatalogAddOnPriority",
398 								B_SYMBOL_TYPE_DATA,
399 								(void**)&prioPtr) == B_OK) {
400 								priority = *prioPtr;
401 								node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0,
402 									&priority, sizeof(int8));
403 							}
404 							unload_add_on(image);
405 						}
406 					}
407 
408 					if (priority >= 0) {
409 						// add-ons with priority < 0 will be ignored
410 						CatalogAddOnInfo* addOnInfo
411 							= new(std::nothrow) CatalogAddOnInfo(dent->d_name,
412 								addOnFolderName, priority);
413 						if (addOnInfo)
414 							fCatalogAddOnInfos.AddItem((void*)addOnInfo);
415 					}
416 				}
417 				// Bump the dirent-pointer by length of the dirent just handled:
418 				dent = (dirent*)((char*)dent + dent->d_reclen);
419 			}
420 		}
421 	}
422 	fCatalogAddOnInfos.SortItems(CompareInfos);
423 
424 	return B_OK;
425 }
426 
427 
428 /*
429  * unloads all catalog-add-ons (which will throw away all loaded catalogs, too)
430  */
431 void
432 LocaleRosterData::_CleanupCatalogAddOns()
433 {
434 	BAutolock lock(fLock);
435 	if (!lock.IsLocked())
436 		return;
437 
438 	int32 count = fCatalogAddOnInfos.CountItems();
439 	for (int32 i = 0; i<count; ++i) {
440 		CatalogAddOnInfo* info
441 			= static_cast<CatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i));
442 		delete info;
443 	}
444 	fCatalogAddOnInfos.MakeEmpty();
445 }
446 
447 
448 status_t
449 LocaleRosterData::_LoadLocaleSettings()
450 {
451 	BPath path;
452 	BFile file;
453 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
454 	if (status == B_OK) {
455 		path.Append("Locale settings");
456 		status = file.SetTo(path.Path(), B_READ_ONLY);
457 	}
458 	BMessage settings;
459 	if (status == B_OK)
460 		status = settings.Unflatten(&file);
461 
462 	if (status == B_OK) {
463 		BFormattingConventions conventions(&settings);
464 		fDefaultLocale.SetFormattingConventions(conventions);
465 
466 		_SetPreferredLanguages(&settings);
467 
468 		bool preferred;
469 		if (settings.FindBool(kTranslateFilesystemField, &preferred) == B_OK)
470 			_SetFilesystemTranslationPreferred(preferred);
471 
472 		return B_OK;
473 	}
474 
475 
476 	// Something went wrong (no settings file or invalid BMessage), so we
477 	// set everything to default values
478 
479 	fPreferredLanguages.MakeEmpty();
480 	fPreferredLanguages.AddString(kLanguageField, "en");
481 	BLanguage defaultLanguage("en_US");
482 	fDefaultLocale.SetLanguage(defaultLanguage);
483 	BFormattingConventions conventions("en_US");
484 	fDefaultLocale.SetFormattingConventions(conventions);
485 
486 	return status;
487 }
488 
489 
490 status_t
491 LocaleRosterData::_LoadTimeSettings()
492 {
493 	BPath path;
494 	BFile file;
495 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
496 	if (status == B_OK) {
497 		path.Append("Time settings");
498 		status = file.SetTo(path.Path(), B_READ_ONLY);
499 	}
500 	BMessage settings;
501 	if (status == B_OK)
502 		status = settings.Unflatten(&file);
503 	if (status == B_OK) {
504 		BString timeZoneID;
505 		if (settings.FindString(kTimezoneField, &timeZoneID) == B_OK)
506 			_SetDefaultTimeZone(BTimeZone(timeZoneID.String()));
507 		else
508 			_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
509 
510 		return B_OK;
511 	}
512 
513 	// Something went wrong (no settings file or invalid BMessage), so we
514 	// set everything to default values
515 	_SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone));
516 
517 	return status;
518 }
519 
520 
521 status_t
522 LocaleRosterData::_SaveLocaleSettings()
523 {
524 	BMessage settings;
525 	status_t status = _AddDefaultFormattingConventionsToMessage(&settings);
526 	if (status == B_OK)
527 		_AddPreferredLanguagesToMessage(&settings);
528 	if (status == B_OK)
529 		_AddFilesystemTranslationPreferenceToMessage(&settings);
530 
531 	BPath path;
532 	if (status == B_OK)
533 		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
534 
535 	BFile file;
536 	if (status == B_OK) {
537 		path.Append("Locale settings");
538 		status = file.SetTo(path.Path(),
539 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
540 	}
541 	if (status == B_OK)
542 		status = settings.Flatten(&file);
543 	if (status == B_OK)
544 		status = file.Sync();
545 
546 	return status;
547 }
548 
549 
550 status_t
551 LocaleRosterData::_SaveTimeSettings()
552 {
553 	BMessage settings;
554 	status_t status = _AddDefaultTimeZoneToMessage(&settings);
555 
556 	BPath path;
557 	if (status == B_OK)
558 		status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
559 
560 	BFile file;
561 	if (status == B_OK) {
562 		path.Append("Time settings");
563 		status = file.SetTo(path.Path(),
564 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
565 	}
566 	if (status == B_OK)
567 		status = settings.Flatten(&file);
568 	if (status == B_OK)
569 		status = file.Sync();
570 
571 	return status;
572 }
573 
574 
575 status_t
576 LocaleRosterData::_SetDefaultFormattingConventions(
577 	const BFormattingConventions& newFormattingConventions)
578 {
579 	fDefaultLocale.SetFormattingConventions(newFormattingConventions);
580 
581 	UErrorCode icuError = U_ZERO_ERROR;
582 	Locale icuLocale = Locale::createCanonical(newFormattingConventions.ID());
583 	if (icuLocale.isBogus())
584 		return B_ERROR;
585 
586 	Locale::setDefault(icuLocale, icuError);
587 	if (!U_SUCCESS(icuError))
588 		return B_ERROR;
589 
590 	return B_OK;
591 }
592 
593 
594 status_t
595 LocaleRosterData::_SetDefaultTimeZone(const BTimeZone& newZone)
596 {
597 	fDefaultTimeZone = newZone;
598 
599 	TimeZone* timeZone = TimeZone::createTimeZone(newZone.ID().String());
600 	if (timeZone == NULL)
601 		return B_ERROR;
602 	TimeZone::adoptDefault(timeZone);
603 
604 	return B_OK;
605 }
606 
607 
608 status_t
609 LocaleRosterData::_SetPreferredLanguages(const BMessage* languages)
610 {
611 	BString langName;
612 	if (languages != NULL
613 		&& languages->FindString(kLanguageField, &langName) == B_OK) {
614 		fDefaultLocale.SetCollator(BCollator(langName.String()));
615 		fDefaultLocale.SetLanguage(BLanguage(langName.String()));
616 
617 		fPreferredLanguages.RemoveName(kLanguageField);
618 		for (int i = 0; languages->FindString(kLanguageField, i, &langName)
619 				== B_OK; i++) {
620 			fPreferredLanguages.AddString(kLanguageField, langName);
621 		}
622 	} else {
623 		fPreferredLanguages.MakeEmpty();
624 		fPreferredLanguages.AddString(kLanguageField, "en");
625 		fDefaultLocale.SetCollator(BCollator("en"));
626 	}
627 
628 	return B_OK;
629 }
630 
631 
632 void
633 LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred)
634 {
635 	fIsFilesystemTranslationPreferred = preferred;
636 }
637 
638 
639 status_t
640 LocaleRosterData::_AddDefaultFormattingConventionsToMessage(
641 	BMessage* message) const
642 {
643 	BFormattingConventions conventions;
644 	fDefaultLocale.GetFormattingConventions(&conventions);
645 
646 	return conventions.Archive(message);
647 }
648 
649 
650 status_t
651 LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const
652 {
653 	return message->AddString(kTimezoneField, fDefaultTimeZone.ID());
654 }
655 
656 
657 status_t
658 LocaleRosterData::_AddPreferredLanguagesToMessage(BMessage* message) const
659 {
660 	status_t status = B_OK;
661 
662 	BString langName;
663 	for (int i = 0; fPreferredLanguages.FindString("language", i,
664 			&langName) == B_OK; i++) {
665 		status = message->AddString(kLanguageField, langName);
666 		if (status != B_OK)
667 			break;
668 	}
669 
670 	return status;
671 }
672 
673 
674 status_t
675 LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage(
676 	BMessage* message) const
677 {
678 	return message->AddBool(kTranslateFilesystemField,
679 		fIsFilesystemTranslationPreferred);
680 }
681 
682 
683 }	// namespace BPrivate
684