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