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