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