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