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 namespace BPrivate { 40 41 42 // #pragma mark - CatalogAddOnInfo 43 44 45 CatalogAddOnInfo::CatalogAddOnInfo(const BString& name, const BString& path, 46 uint8 priority) 47 : 48 fInstantiateFunc(NULL), 49 fCreateFunc(NULL), 50 fLanguagesFunc(NULL), 51 fName(name), 52 fPath(path), 53 fAddOnImage(B_NO_INIT), 54 fPriority(priority), 55 fIsEmbedded(path.Length()==0) 56 { 57 } 58 59 60 CatalogAddOnInfo::~CatalogAddOnInfo() 61 { 62 int32 count = fLoadedCatalogs.CountItems(); 63 for (int32 i = 0; i < count; ++i) { 64 BCatalogData* cat 65 = static_cast<BCatalogData*>(fLoadedCatalogs.ItemAt(i)); 66 delete cat; 67 } 68 fLoadedCatalogs.MakeEmpty(); 69 UnloadIfPossible(); 70 } 71 72 73 bool 74 CatalogAddOnInfo::MakeSureItsLoaded() 75 { 76 if (!fIsEmbedded && fAddOnImage < B_OK) { 77 // add-on has not been loaded yet, so we try to load it: 78 BString fullAddOnPath(fPath); 79 fullAddOnPath << "/" << fName; 80 fAddOnImage = load_add_on(fullAddOnPath.String()); 81 if (fAddOnImage >= B_OK) { 82 get_image_symbol(fAddOnImage, "instantiate_catalog", 83 B_SYMBOL_TYPE_TEXT, (void**)&fInstantiateFunc); 84 get_image_symbol(fAddOnImage, "create_catalog", 85 B_SYMBOL_TYPE_TEXT, (void**)&fCreateFunc); 86 get_image_symbol(fAddOnImage, "get_available_languages", 87 B_SYMBOL_TYPE_TEXT, (void**)&fLanguagesFunc); 88 } else 89 return false; 90 } else if (fIsEmbedded) { 91 // The built-in catalog still has to provide this function 92 fLanguagesFunc = default_catalog_get_available_languages; 93 } 94 return true; 95 } 96 97 98 void 99 CatalogAddOnInfo::UnloadIfPossible() 100 { 101 if (!fIsEmbedded && fLoadedCatalogs.IsEmpty()) { 102 unload_add_on(fAddOnImage); 103 fAddOnImage = B_NO_INIT; 104 fInstantiateFunc = NULL; 105 fCreateFunc = NULL; 106 fLanguagesFunc = NULL; 107 } 108 } 109 110 111 // #pragma mark - LocaleRosterData 112 113 114 namespace { 115 116 117 static const char* kPriorityAttr = "ADDON:priority"; 118 119 static const char* kLanguageField = "language"; 120 static const char* kTimezoneField = "timezone"; 121 static const char* kTranslateFilesystemField = "filesys"; 122 123 124 } // anonymous namespace 125 126 127 LocaleRosterData::LocaleRosterData(const BLanguage& language, 128 const BFormattingConventions& conventions) 129 : 130 fLock("LocaleRosterData"), 131 fDefaultLocale(&language, &conventions), 132 fIsFilesystemTranslationPreferred(false), 133 fAreResourcesLoaded(false) 134 { 135 fInitStatus = _Initialize(); 136 } 137 138 139 LocaleRosterData::~LocaleRosterData() 140 { 141 BAutolock lock(fLock); 142 143 _CleanupCatalogAddOns(); 144 } 145 146 147 status_t 148 LocaleRosterData::InitCheck() const 149 { 150 return fAreResourcesLoaded ? B_OK : B_NO_INIT; 151 } 152 153 154 status_t 155 LocaleRosterData::Refresh() 156 { 157 BAutolock lock(fLock); 158 if (!lock.IsLocked()) 159 return B_ERROR; 160 161 _LoadLocaleSettings(); 162 _LoadTimeSettings(); 163 164 return B_OK; 165 } 166 167 168 int 169 LocaleRosterData::CompareInfos(const void* left, const void* right) 170 { 171 const CatalogAddOnInfo* leftInfo 172 = * static_cast<const CatalogAddOnInfo* const *>(left); 173 const CatalogAddOnInfo* rightInfo 174 = * static_cast<const CatalogAddOnInfo* const *>(right); 175 176 return leftInfo->fPriority - rightInfo->fPriority; 177 } 178 179 180 status_t 181 LocaleRosterData::SetDefaultFormattingConventions( 182 const BFormattingConventions& newFormattingConventions) 183 { 184 status_t status = B_OK; 185 186 BAutolock lock(fLock); 187 if (!lock.IsLocked()) 188 return B_ERROR; 189 190 status = _SetDefaultFormattingConventions(newFormattingConventions); 191 192 if (status == B_OK) 193 status = _SaveLocaleSettings(); 194 195 if (status == B_OK) { 196 BMessage updateMessage(B_LOCALE_CHANGED); 197 status = _AddDefaultFormattingConventionsToMessage(&updateMessage); 198 if (status == B_OK) 199 status = be_roster->Broadcast(&updateMessage); 200 } 201 202 return status; 203 } 204 205 206 status_t 207 LocaleRosterData::SetDefaultTimeZone(const BTimeZone& newZone) 208 { 209 status_t status = B_OK; 210 211 BAutolock lock(fLock); 212 if (!lock.IsLocked()) 213 return B_ERROR; 214 215 status = _SetDefaultTimeZone(newZone); 216 217 if (status == B_OK) 218 status = _SaveTimeSettings(); 219 220 if (status == B_OK) { 221 BMessage updateMessage(B_LOCALE_CHANGED); 222 status = _AddDefaultTimeZoneToMessage(&updateMessage); 223 if (status == B_OK) 224 status = be_roster->Broadcast(&updateMessage); 225 } 226 227 return status; 228 } 229 230 231 status_t 232 LocaleRosterData::SetPreferredLanguages(const BMessage* languages) 233 { 234 status_t status = B_OK; 235 236 BAutolock lock(fLock); 237 if (!lock.IsLocked()) 238 return B_ERROR; 239 240 status = _SetPreferredLanguages(languages); 241 242 if (status == B_OK) 243 status = _SaveLocaleSettings(); 244 245 if (status == B_OK) { 246 BMessage updateMessage(B_LOCALE_CHANGED); 247 status = _AddPreferredLanguagesToMessage(&updateMessage); 248 if (status == B_OK) 249 status = be_roster->Broadcast(&updateMessage); 250 } 251 252 return status; 253 } 254 255 256 status_t 257 LocaleRosterData::SetFilesystemTranslationPreferred(bool preferred) 258 { 259 BAutolock lock(fLock); 260 if (!lock.IsLocked()) 261 return B_ERROR; 262 263 _SetFilesystemTranslationPreferred(preferred); 264 265 status_t status = _SaveLocaleSettings(); 266 267 if (status == B_OK) { 268 BMessage updateMessage(B_LOCALE_CHANGED); 269 status = _AddFilesystemTranslationPreferenceToMessage(&updateMessage); 270 if (status == B_OK) 271 status = be_roster->Broadcast(&updateMessage); 272 } 273 274 return status; 275 } 276 277 278 status_t 279 LocaleRosterData::GetResources(BResources** resources) 280 { 281 if (resources == NULL) 282 return B_BAD_VALUE; 283 284 if (!fAreResourcesLoaded) { 285 status_t result 286 = fResources.SetToImage((const void*)&BLocaleRoster::Default); 287 if (result != B_OK) 288 return result; 289 290 result = fResources.PreloadResourceType(); 291 if (result != B_OK) 292 return result; 293 294 fAreResourcesLoaded = true; 295 } 296 297 *resources = &fResources; 298 return B_OK; 299 } 300 301 302 status_t 303 LocaleRosterData::_Initialize() 304 { 305 status_t result = _InitializeCatalogAddOns(); 306 if (result != B_OK) 307 return result; 308 309 if ((result = Refresh()) != B_OK) 310 return result; 311 312 fInitStatus = B_OK; 313 return B_OK; 314 } 315 316 317 /* 318 iterate over add-on-folders and collect information about each 319 catalog-add-ons (types of catalogs) into fCatalogAddOnInfos. 320 */ 321 status_t 322 LocaleRosterData::_InitializeCatalogAddOns() 323 { 324 BAutolock lock(fLock); 325 if (!lock.IsLocked()) 326 return B_ERROR; 327 328 // add info about embedded default catalog: 329 CatalogAddOnInfo* defaultCatalogAddOnInfo 330 = new(std::nothrow) CatalogAddOnInfo("Default", "", 331 DefaultCatalog::kDefaultCatalogAddOnPriority); 332 if (!defaultCatalogAddOnInfo) 333 return B_NO_MEMORY; 334 335 defaultCatalogAddOnInfo->fInstantiateFunc = DefaultCatalog::Instantiate; 336 defaultCatalogAddOnInfo->fCreateFunc = DefaultCatalog::Create; 337 fCatalogAddOnInfos.AddItem((void*)defaultCatalogAddOnInfo); 338 339 BStringList folders; 340 BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "locale/catalogs/", 341 B_FIND_PATH_EXISTING_ONLY, folders); 342 343 BPath addOnPath; 344 BDirectory addOnFolder; 345 char buf[4096]; 346 status_t err; 347 for (int32 f = 0; f < folders.CountStrings(); f++) { 348 BString addOnFolderName = folders.StringAt(f); 349 err = addOnFolder.SetTo(addOnFolderName.String()); 350 if (err != B_OK) 351 continue; 352 353 // scan through all the folder's entries for catalog add-ons: 354 int32 count; 355 int8 priority; 356 entry_ref eref; 357 BNode node; 358 BEntry entry; 359 dirent* dent; 360 while ((count = addOnFolder.GetNextDirents((dirent*)buf, sizeof(buf))) 361 > 0) { 362 dent = (dirent*)buf; 363 while (count-- > 0) { 364 if (strcmp(dent->d_name, ".") != 0 365 && strcmp(dent->d_name, "..") != 0 366 && strcmp(dent->d_name, "x86") != 0 367 && strcmp(dent->d_name, "x86_gcc2") != 0) { 368 // we have found (what should be) a catalog-add-on: 369 eref.device = dent->d_pdev; 370 eref.directory = dent->d_pino; 371 eref.set_name(dent->d_name); 372 entry.SetTo(&eref, true); 373 // traverse through any links to get to the real thang! 374 node.SetTo(&entry); 375 priority = -1; 376 if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0, 377 &priority, sizeof(int8)) <= 0) { 378 // add-on has no priority-attribute yet, so we load it 379 // to fetch the priority from the corresponding 380 // symbol... 381 BString fullAddOnPath(addOnFolderName); 382 fullAddOnPath << "/" << dent->d_name; 383 image_id image = load_add_on(fullAddOnPath.String()); 384 if (image >= B_OK) { 385 uint8* prioPtr; 386 if (get_image_symbol(image, "gCatalogAddOnPriority", 387 B_SYMBOL_TYPE_DATA, 388 (void**)&prioPtr) == B_OK) { 389 priority = *prioPtr; 390 node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0, 391 &priority, sizeof(int8)); 392 } 393 unload_add_on(image); 394 } 395 } 396 397 if (priority >= 0) { 398 // add-ons with priority < 0 will be ignored 399 CatalogAddOnInfo* addOnInfo 400 = new(std::nothrow) CatalogAddOnInfo(dent->d_name, 401 addOnFolderName, priority); 402 if (addOnInfo) 403 fCatalogAddOnInfos.AddItem((void*)addOnInfo); 404 } 405 } 406 // Bump the dirent-pointer by length of the dirent just handled: 407 dent = (dirent*)((char*)dent + dent->d_reclen); 408 } 409 } 410 } 411 fCatalogAddOnInfos.SortItems(CompareInfos); 412 413 return B_OK; 414 } 415 416 417 /* 418 * unloads all catalog-add-ons (which will throw away all loaded catalogs, too) 419 */ 420 void 421 LocaleRosterData::_CleanupCatalogAddOns() 422 { 423 BAutolock lock(fLock); 424 if (!lock.IsLocked()) 425 return; 426 427 int32 count = fCatalogAddOnInfos.CountItems(); 428 for (int32 i = 0; i<count; ++i) { 429 CatalogAddOnInfo* info 430 = static_cast<CatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i)); 431 delete info; 432 } 433 fCatalogAddOnInfos.MakeEmpty(); 434 } 435 436 437 status_t 438 LocaleRosterData::_LoadLocaleSettings() 439 { 440 BPath path; 441 BFile file; 442 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 443 if (status == B_OK) { 444 path.Append("Locale settings"); 445 status = file.SetTo(path.Path(), B_READ_ONLY); 446 } 447 BMessage settings; 448 if (status == B_OK) 449 status = settings.Unflatten(&file); 450 451 if (status == B_OK) { 452 BFormattingConventions conventions(&settings); 453 fDefaultLocale.SetFormattingConventions(conventions); 454 455 _SetPreferredLanguages(&settings); 456 457 bool preferred; 458 if (settings.FindBool(kTranslateFilesystemField, &preferred) == B_OK) 459 _SetFilesystemTranslationPreferred(preferred); 460 461 return B_OK; 462 } 463 464 465 // Something went wrong (no settings file or invalid BMessage), so we 466 // set everything to default values 467 468 fPreferredLanguages.MakeEmpty(); 469 fPreferredLanguages.AddString(kLanguageField, "en"); 470 BLanguage defaultLanguage("en_US"); 471 fDefaultLocale.SetLanguage(defaultLanguage); 472 BFormattingConventions conventions("en_US"); 473 fDefaultLocale.SetFormattingConventions(conventions); 474 475 return status; 476 } 477 478 479 status_t 480 LocaleRosterData::_LoadTimeSettings() 481 { 482 BPath path; 483 BFile file; 484 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 485 if (status == B_OK) { 486 path.Append("Time settings"); 487 status = file.SetTo(path.Path(), B_READ_ONLY); 488 } 489 BMessage settings; 490 if (status == B_OK) 491 status = settings.Unflatten(&file); 492 if (status == B_OK) { 493 BString timeZoneID; 494 if (settings.FindString(kTimezoneField, &timeZoneID) == B_OK) 495 _SetDefaultTimeZone(BTimeZone(timeZoneID.String())); 496 else 497 _SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone)); 498 499 return B_OK; 500 } 501 502 // Something went wrong (no settings file or invalid BMessage), so we 503 // set everything to default values 504 _SetDefaultTimeZone(BTimeZone(BTimeZone::kNameOfGmtZone)); 505 506 return status; 507 } 508 509 510 status_t 511 LocaleRosterData::_SaveLocaleSettings() 512 { 513 BMessage settings; 514 status_t status = _AddDefaultFormattingConventionsToMessage(&settings); 515 if (status == B_OK) 516 _AddPreferredLanguagesToMessage(&settings); 517 if (status == B_OK) 518 _AddFilesystemTranslationPreferenceToMessage(&settings); 519 520 BPath path; 521 if (status == B_OK) 522 status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 523 524 BFile file; 525 if (status == B_OK) { 526 path.Append("Locale settings"); 527 status = file.SetTo(path.Path(), 528 B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); 529 } 530 if (status == B_OK) 531 status = settings.Flatten(&file); 532 if (status == B_OK) 533 status = file.Sync(); 534 535 return status; 536 } 537 538 539 status_t 540 LocaleRosterData::_SaveTimeSettings() 541 { 542 BMessage settings; 543 status_t status = _AddDefaultTimeZoneToMessage(&settings); 544 545 BPath path; 546 if (status == B_OK) 547 status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 548 549 BFile file; 550 if (status == B_OK) { 551 path.Append("Time settings"); 552 status = file.SetTo(path.Path(), 553 B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); 554 } 555 if (status == B_OK) 556 status = settings.Flatten(&file); 557 if (status == B_OK) 558 status = file.Sync(); 559 560 return status; 561 } 562 563 564 status_t 565 LocaleRosterData::_SetDefaultFormattingConventions( 566 const BFormattingConventions& newFormattingConventions) 567 { 568 fDefaultLocale.SetFormattingConventions(newFormattingConventions); 569 570 UErrorCode icuError = U_ZERO_ERROR; 571 Locale icuLocale = Locale::createCanonical(newFormattingConventions.ID()); 572 if (icuLocale.isBogus()) 573 return B_ERROR; 574 575 Locale::setDefault(icuLocale, icuError); 576 if (!U_SUCCESS(icuError)) 577 return B_ERROR; 578 579 return B_OK; 580 } 581 582 583 status_t 584 LocaleRosterData::_SetDefaultTimeZone(const BTimeZone& newZone) 585 { 586 fDefaultTimeZone = newZone; 587 588 TimeZone* timeZone = TimeZone::createTimeZone(newZone.ID().String()); 589 if (timeZone == NULL) 590 return B_ERROR; 591 TimeZone::adoptDefault(timeZone); 592 593 return B_OK; 594 } 595 596 597 status_t 598 LocaleRosterData::_SetPreferredLanguages(const BMessage* languages) 599 { 600 BString langName; 601 if (languages != NULL 602 && languages->FindString(kLanguageField, &langName) == B_OK) { 603 fDefaultLocale.SetCollator(BCollator(langName.String())); 604 fDefaultLocale.SetLanguage(BLanguage(langName.String())); 605 606 fPreferredLanguages.RemoveName(kLanguageField); 607 for (int i = 0; languages->FindString(kLanguageField, i, &langName) 608 == B_OK; i++) { 609 fPreferredLanguages.AddString(kLanguageField, langName); 610 } 611 } else { 612 fPreferredLanguages.MakeEmpty(); 613 fPreferredLanguages.AddString(kLanguageField, "en"); 614 fDefaultLocale.SetCollator(BCollator("en")); 615 } 616 617 return B_OK; 618 } 619 620 621 void 622 LocaleRosterData::_SetFilesystemTranslationPreferred(bool preferred) 623 { 624 fIsFilesystemTranslationPreferred = preferred; 625 } 626 627 628 status_t 629 LocaleRosterData::_AddDefaultFormattingConventionsToMessage( 630 BMessage* message) const 631 { 632 BFormattingConventions conventions; 633 fDefaultLocale.GetFormattingConventions(&conventions); 634 635 return conventions.Archive(message); 636 } 637 638 639 status_t 640 LocaleRosterData::_AddDefaultTimeZoneToMessage(BMessage* message) const 641 { 642 return message->AddString(kTimezoneField, fDefaultTimeZone.ID()); 643 } 644 645 646 status_t 647 LocaleRosterData::_AddPreferredLanguagesToMessage(BMessage* message) const 648 { 649 status_t status = B_OK; 650 651 BString langName; 652 for (int i = 0; fPreferredLanguages.FindString("language", i, 653 &langName) == B_OK; i++) { 654 status = message->AddString(kLanguageField, langName); 655 if (status != B_OK) 656 break; 657 } 658 659 return status; 660 } 661 662 663 status_t 664 LocaleRosterData::_AddFilesystemTranslationPreferenceToMessage( 665 BMessage* message) const 666 { 667 return message->AddBool(kTranslateFilesystemField, 668 fIsFilesystemTranslationPreferred); 669 } 670 671 672 } // namespace BPrivate 673