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