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->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 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 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 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 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 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 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 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 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 630 LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred) 631 { 632 fIsFilesystemTranslationPreferred = preferred; 633 } 634 635 636 status_t 637 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 648 LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const 649 { 650 return message->AddString(kTimezoneField, fDefaultTimeZone.ID()); 651 } 652 653 654 status_t 655 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 672 LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage( 673 BMessage* message) const 674 { 675 return message->AddBool(kTranslateFilesystemField, 676 fIsFilesystemTranslationPreferred); 677 } 678 679 680 } // namespace BPrivate 681