1 /* 2 * Copyright 2003-2004, 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 <LocaleRoster.h> 12 13 #include <set> 14 15 #include <assert.h> 16 #include <stdio.h> // for debug only 17 #include <syslog.h> 18 19 #include <Autolock.h> 20 #include <Catalog.h> 21 #include <Collator.h> 22 #include <Country.h> 23 #include <DefaultCatalog.h> 24 #include <Directory.h> 25 #include <Entry.h> 26 #include <File.h> 27 #include <FindDirectory.h> 28 #include <Language.h> 29 #include <Locale.h> 30 #include <Node.h> 31 #include <Path.h> 32 #include <String.h> 33 34 #include <ICUWrapper.h> 35 36 // ICU includes 37 #include <unicode/locid.h> 38 39 40 static const char *kPriorityAttr = "ADDON:priority"; 41 42 typedef BCatalogAddOn *(*InstantiateCatalogFunc)(const char *name, 43 const char *language, uint32 fingerprint); 44 45 typedef BCatalogAddOn *(*CreateCatalogFunc)(const char *name, 46 const char *language); 47 48 typedef BCatalogAddOn *(*InstantiateEmbeddedCatalogFunc)( 49 entry_ref *appOrAddOnRef); 50 51 static BLocaleRoster gLocaleRoster; 52 BLocaleRoster *be_locale_roster = &gLocaleRoster; 53 54 55 /* 56 * info about a single catalog-add-on (representing a catalog type): 57 */ 58 struct BCatalogAddOnInfo { 59 BString fName; 60 BString fPath; 61 image_id fAddOnImage; 62 InstantiateCatalogFunc fInstantiateFunc; 63 InstantiateEmbeddedCatalogFunc fInstantiateEmbeddedFunc; 64 CreateCatalogFunc fCreateFunc; 65 uint8 fPriority; 66 BList fLoadedCatalogs; 67 bool fIsEmbedded; 68 // an embedded add-on actually isn't an add-on, it is included 69 // as part of the library. The DefaultCatalog is such a beast! 70 71 BCatalogAddOnInfo(const BString& name, const BString& path, uint8 priority); 72 ~BCatalogAddOnInfo(); 73 bool MakeSureItsLoaded(); 74 void UnloadIfPossible(); 75 }; 76 77 78 BCatalogAddOnInfo::BCatalogAddOnInfo(const BString& name, const BString& path, 79 uint8 priority) 80 : 81 fName(name), 82 fPath(path), 83 fAddOnImage(B_NO_INIT), 84 fInstantiateFunc(NULL), 85 fInstantiateEmbeddedFunc(NULL), 86 fCreateFunc(NULL), 87 fPriority(priority), 88 fIsEmbedded(path.Length()==0) 89 { 90 } 91 92 93 BCatalogAddOnInfo::~BCatalogAddOnInfo() 94 { 95 int32 count = fLoadedCatalogs.CountItems(); 96 for (int32 i = 0; i < count; ++i) { 97 BCatalogAddOn* cat 98 = static_cast<BCatalogAddOn*>(fLoadedCatalogs.ItemAt(i)); 99 delete cat; 100 } 101 fLoadedCatalogs.MakeEmpty(); 102 UnloadIfPossible(); 103 } 104 105 106 bool 107 BCatalogAddOnInfo::MakeSureItsLoaded() 108 { 109 if (!fIsEmbedded && fAddOnImage < B_OK) { 110 // add-on has not been loaded yet, so we try to load it: 111 BString fullAddOnPath(fPath); 112 fullAddOnPath << "/" << fName; 113 fAddOnImage = load_add_on(fullAddOnPath.String()); 114 if (fAddOnImage >= B_OK) { 115 get_image_symbol(fAddOnImage, "instantiate_catalog", 116 B_SYMBOL_TYPE_TEXT, (void **)&fInstantiateFunc); 117 get_image_symbol(fAddOnImage, "instantiate_embedded_catalog", 118 B_SYMBOL_TYPE_TEXT, (void **)&fInstantiateEmbeddedFunc); 119 get_image_symbol(fAddOnImage, "create_catalog", 120 B_SYMBOL_TYPE_TEXT, (void **)&fCreateFunc); 121 log_team(LOG_DEBUG, "catalog-add-on %s has been loaded", 122 fName.String()); 123 } else { 124 log_team(LOG_DEBUG, "could not load catalog-add-on %s (%s)", 125 fName.String(), strerror(fAddOnImage)); 126 return false; 127 } 128 } 129 return true; 130 } 131 132 133 void 134 BCatalogAddOnInfo::UnloadIfPossible() 135 { 136 if (!fIsEmbedded && fLoadedCatalogs.IsEmpty()) { 137 unload_add_on(fAddOnImage); 138 fAddOnImage = B_NO_INIT; 139 fInstantiateFunc = NULL; 140 fInstantiateEmbeddedFunc = NULL; 141 fCreateFunc = NULL; 142 log_team(LOG_DEBUG, "catalog-add-on %s has been unloaded", 143 fName.String()); 144 } 145 } 146 147 148 /* 149 * the global data that is shared between all roster-objects: 150 */ 151 struct RosterData { 152 BLocker fLock; 153 BList fCatalogAddOnInfos; 154 BMessage fPreferredLanguages; 155 BString fCountryCodeName; 156 BString fCountryDateFormat; 157 158 RosterData(); 159 ~RosterData(); 160 void InitializeCatalogAddOns(); 161 void CleanupCatalogAddOns(); 162 static int CompareInfos(const void *left, const void *right); 163 }; 164 static RosterData gRosterData; 165 166 167 RosterData::RosterData() 168 : 169 fLock("LocaleRosterData") 170 { 171 BAutolock lock(fLock); 172 assert(lock.IsLocked()); 173 174 // TODO: make a decision about log-facility and -options 175 openlog_team("liblocale.so", LOG_PID, LOG_USER); 176 #ifndef DEBUG 177 // TODO: find out why the following bugger isn't working! 178 setlogmask_team(LOG_UPTO(LOG_WARNING)); 179 #endif 180 181 InitializeCatalogAddOns(); 182 183 // Load preferences to get the preferred languages 184 BPath path; 185 BFile file; 186 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 187 path.Append("Locale settings"); 188 BMessage settingsMessage; 189 if (file.SetTo(path.Path(), B_READ_ONLY) == B_OK 190 && settingsMessage.Unflatten(&file) == B_OK) { 191 BString langName; 192 if (settingsMessage.FindString("language", &langName) == B_OK) { 193 UErrorCode icuError = U_ZERO_ERROR; 194 Locale icuLocale = Locale::createCanonical(langName.String()); 195 assert(!icuLocale.isBogus()); 196 UnicodeString ustr; 197 BString bstr; 198 BStringByteSink bbs(&bstr); 199 icuLocale.getDisplayName(ustr); 200 ustr.toUTF8(bbs); 201 202 Locale::setDefault(icuLocale,icuError); 203 assert(icuError == U_ZERO_ERROR); 204 fPreferredLanguages.RemoveName("language"); 205 for (int i = 0; settingsMessage.FindString("language", i, 206 &langName) == B_OK; i++) { 207 fPreferredLanguages.AddString("language", langName); 208 } 209 } else 210 fPreferredLanguages.AddString("language", "en"); 211 212 if (settingsMessage.FindString("country", &fCountryCodeName) != B_OK) 213 fCountryCodeName = "en_US"; 214 return; 215 } 216 } 217 218 // Something went wrong (no settings file or invalid BMessage 219 // set everything to default values 220 fPreferredLanguages.AddString("language", "en"); 221 fCountryCodeName = "en_US"; 222 log_team(LOG_ERR,"*** No language preference found!\n"); 223 } 224 225 226 RosterData::~RosterData() 227 { 228 BAutolock lock(fLock); 229 assert(lock.IsLocked()); 230 CleanupCatalogAddOns(); 231 closelog(); 232 } 233 234 235 int 236 RosterData::CompareInfos(const void *left, const void *right) 237 { 238 return ((BCatalogAddOnInfo*)right)->fPriority 239 - ((BCatalogAddOnInfo*)left)->fPriority; 240 } 241 242 243 /* 244 * iterate over add-on-folders and collect information about each 245 * catalog-add-ons (types of catalogs) into fCatalogAddOnInfos. 246 */ 247 void 248 RosterData::InitializeCatalogAddOns() 249 { 250 BAutolock lock(fLock); 251 assert(lock.IsLocked()); 252 253 // add info about embedded default catalog: 254 BCatalogAddOnInfo *defaultCatalogAddOnInfo 255 = new(std::nothrow) BCatalogAddOnInfo("Default", "", 256 DefaultCatalog::kDefaultCatalogAddOnPriority); 257 if (!defaultCatalogAddOnInfo) 258 return; 259 260 defaultCatalogAddOnInfo->fInstantiateFunc = DefaultCatalog::Instantiate; 261 defaultCatalogAddOnInfo->fInstantiateEmbeddedFunc 262 = DefaultCatalog::InstantiateEmbedded; 263 defaultCatalogAddOnInfo->fCreateFunc = DefaultCatalog::Create; 264 fCatalogAddOnInfos.AddItem((void*)defaultCatalogAddOnInfo); 265 266 directory_which folders[] = { 267 B_COMMON_ADDONS_DIRECTORY, 268 B_SYSTEM_ADDONS_DIRECTORY, 269 static_cast<directory_which>(-1) 270 }; 271 BPath addOnPath; 272 BDirectory addOnFolder; 273 char buf[4096]; 274 status_t err; 275 for (int f=0; folders[f]>=0; ++f) { 276 find_directory(folders[f], &addOnPath); 277 BString addOnFolderName(addOnPath.Path()); 278 addOnFolderName << "/locale/catalogs"; 279 err = addOnFolder.SetTo(addOnFolderName.String()); 280 if (err != B_OK) 281 continue; 282 283 // scan through all the folder's entries for catalog add-ons: 284 int32 count; 285 int8 priority; 286 entry_ref eref; 287 BNode node; 288 BEntry entry; 289 dirent *dent; 290 while ((count = addOnFolder.GetNextDirents((dirent *)buf, 4096)) > 0) { 291 dent = (dirent *)buf; 292 while (count-- > 0) { 293 if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) { 294 // we have found (what should be) a catalog-add-on: 295 eref.device = dent->d_pdev; 296 eref.directory = dent->d_pino; 297 eref.set_name(dent->d_name); 298 entry.SetTo(&eref, true); 299 // traverse through any links to get to the real thang! 300 node.SetTo(&entry); 301 priority = -1; 302 if (node.ReadAttr(kPriorityAttr, B_INT8_TYPE, 0, 303 &priority, sizeof(int8)) <= 0) { 304 // add-on has no priority-attribute yet, so we load it 305 // to fetch the priority from the corresponding 306 // symbol... 307 BString fullAddOnPath(addOnFolderName); 308 fullAddOnPath << "/" << dent->d_name; 309 image_id image = load_add_on(fullAddOnPath.String()); 310 if (image >= B_OK) { 311 uint8 *prioPtr; 312 if (get_image_symbol(image, "gCatalogAddOnPriority", 313 B_SYMBOL_TYPE_DATA, 314 (void **)&prioPtr) == B_OK) { 315 priority = *prioPtr; 316 node.WriteAttr(kPriorityAttr, B_INT8_TYPE, 0, 317 &priority, sizeof(int8)); 318 } else { 319 log_team(LOG_ERR, 320 "couldn't get priority for add-on %s\n", 321 fullAddOnPath.String()); 322 } 323 unload_add_on(image); 324 } else { 325 log_team(LOG_ERR, 326 "couldn't load add-on %s, error: %s\n", 327 fullAddOnPath.String(), strerror(image)); 328 } 329 } 330 331 log_team(LOG_ERR, "Found : %s priority: %d\n", 332 dent->d_name,priority); 333 334 if (priority >= 0) { 335 // add-ons with priority < 0 will be ignored 336 BCatalogAddOnInfo* addOnInfo 337 = new(std::nothrow) BCatalogAddOnInfo(dent->d_name, 338 addOnFolderName, priority); 339 if (addOnInfo) 340 fCatalogAddOnInfos.AddItem((void*)addOnInfo); 341 } 342 } 343 // Bump the dirent-pointer by length of the dirent just handled: 344 dent = (dirent *)((char *)dent + dent->d_reclen); 345 } 346 } 347 } 348 fCatalogAddOnInfos.SortItems(CompareInfos); 349 350 for (int32 i=0; i<fCatalogAddOnInfos.CountItems(); ++i) { 351 BCatalogAddOnInfo *info 352 = static_cast<BCatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i)); 353 log_team(LOG_INFO, 354 "roster uses catalog-add-on %s/%s with priority %d", 355 info->fIsEmbedded ? "(embedded)" : info->fPath.String(), 356 info->fName.String(), info->fPriority); 357 } 358 } 359 360 361 /* 362 * unloads all catalog-add-ons (which will throw away all loaded catalogs, too) 363 */ 364 void 365 RosterData::CleanupCatalogAddOns() 366 { 367 BAutolock lock(fLock); 368 assert(lock.IsLocked()); 369 int32 count = fCatalogAddOnInfos.CountItems(); 370 for (int32 i=0; i<count; ++i) { 371 BCatalogAddOnInfo *info 372 = static_cast<BCatalogAddOnInfo*>(fCatalogAddOnInfos.ItemAt(i)); 373 delete info; 374 } 375 fCatalogAddOnInfos.MakeEmpty(); 376 } 377 378 379 /* 380 * several attributes/resource-IDs used within the Locale Kit: 381 */ 382 const char *BLocaleRoster::kCatLangAttr = "BEOS:LOCALE_LANGUAGE"; 383 // name of catalog language, lives in every catalog file 384 const char *BLocaleRoster::kCatSigAttr = "BEOS:LOCALE_SIGNATURE"; 385 // catalog signature, lives in every catalog file 386 const char *BLocaleRoster::kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT"; 387 // catalog fingerprint, may live in catalog file 388 389 const char *BLocaleRoster::kCatManagerMimeType 390 = "application/x-vnd.Be.locale.catalog-manager"; 391 // signature of catalog managing app 392 const char *BLocaleRoster::kCatEditorMimeType 393 = "application/x-vnd.Be.locale.catalog-editor"; 394 // signature of catalog editor app 395 396 const char *BLocaleRoster::kEmbeddedCatAttr = "BEOS:LOCALE_EMBEDDED_CATALOG"; 397 // attribute which contains flattened data of embedded catalog 398 // this may live in an app- or add-on-file 399 int32 BLocaleRoster::kEmbeddedCatResId = 0xCADA; 400 // a unique value used to identify the resource (=> embedded CAtalog DAta) 401 // which contains flattened data of embedded catalog. 402 // this may live in an app- or add-on-file 403 404 /* 405 * BLocaleRoster, the exported interface to the locale roster data: 406 */ 407 BLocaleRoster::BLocaleRoster() 408 { 409 } 410 411 412 BLocaleRoster::~BLocaleRoster() 413 { 414 } 415 416 417 status_t 418 BLocaleRoster::GetDefaultCollator(BCollator **collator) const 419 { 420 // It should just use the archived collator from the locale settings; 421 // if that is not available, just return the standard collator 422 if (!collator) 423 return B_BAD_VALUE; 424 *collator = new(std::nothrow) BCollator(); 425 return B_OK; 426 } 427 428 429 status_t 430 BLocaleRoster::GetDefaultLanguage(BLanguage **language) const 431 { 432 if (!language) 433 return B_BAD_VALUE; 434 *language = new(std::nothrow) BLanguage(NULL); 435 return B_OK; 436 } 437 438 439 status_t 440 BLocaleRoster::GetDefaultCountry(BCountry **country) const 441 { 442 if (!country) 443 return B_BAD_VALUE; 444 445 BAutolock lock(gRosterData.fLock); 446 assert(lock.IsLocked()); 447 448 *country = new(std::nothrow) BCountry( 449 gRosterData.fCountryCodeName.String()); 450 if (gRosterData.fCountryDateFormat.Length() > 0) 451 (*country)->SetDateFormat(gRosterData.fCountryDateFormat.String()); 452 return B_OK; 453 } 454 455 456 void 457 BLocaleRoster::SetDefaultCountry(BCountry * newDefault) const 458 { 459 gRosterData.fCountryCodeName = newDefault->Code(); 460 newDefault->DateFormat(gRosterData.fCountryDateFormat, true); 461 } 462 463 464 status_t 465 BLocaleRoster::GetPreferredLanguages(BMessage *languages) const 466 { 467 if (!languages) 468 return B_BAD_VALUE; 469 470 BAutolock lock(gRosterData.fLock); 471 assert(lock.IsLocked()); 472 473 *languages = gRosterData.fPreferredLanguages; 474 return B_OK; 475 } 476 477 478 status_t 479 BLocaleRoster::SetPreferredLanguages(BMessage *languages) 480 { 481 BAutolock lock(gRosterData.fLock); 482 assert(lock.IsLocked()); 483 484 if (languages) 485 gRosterData.fPreferredLanguages = *languages; 486 else 487 gRosterData.fPreferredLanguages.MakeEmpty(); 488 return B_OK; 489 } 490 491 492 // Get all the available languages from ICU 493 status_t 494 BLocaleRoster::GetInstalledLanguages(BMessage *languages) const 495 { 496 if (!languages) 497 return B_BAD_VALUE; 498 499 int32 i; 500 UnicodeString icuLanguageName; 501 BString languageName; 502 std::set<BString> languageSet; 503 504 #undef REALLY_ALL_LANGUAGES 505 #ifdef REALLY_ALL_LANGUAGES 506 static const char* const* icuLocaleList = Locale::getISOLanguages(); 507 508 // Loop over the strings and add them to an std::set to remove duplicates 509 for (i = 0; icuLocaleList[i]; i++) { 510 languageSet.insert(BString(icuLocaleList[i])); 511 } 512 #else 513 int32_t localeCount; 514 static const Locale* icuLocaleList 515 = Locale::getAvailableLocales(localeCount); 516 517 // Loop over the strings and add them to an std::set to remove duplicates 518 for (i = 0; i < localeCount; i++) { 519 languageSet.insert(icuLocaleList[i].getLanguage()); 520 } 521 #endif 522 523 std::set<BString>::const_iterator lastLang = languageSet.end(); 524 for (std::set<BString>::const_iterator setIterator = languageSet.begin(); 525 setIterator != lastLang; setIterator++) { 526 languages->AddString("langs", *setIterator); 527 } 528 529 return B_OK; 530 } 531 532 533 /* 534 * creates a new (empty) catalog of the given type (the request is dispatched 535 * to the appropriate add-on). 536 * If the add-on doesn't support catalog-creation or if the creation fails, 537 * NULL is returned, otherwise a pointer to the freshly created catalog. 538 * Any created catalog will be initialized with the given signature and 539 * language-name. 540 */ 541 BCatalogAddOn* 542 BLocaleRoster::CreateCatalog(const char *type, const char *signature, 543 const char *language) 544 { 545 if (!type || !signature || !language) 546 return NULL; 547 548 BAutolock lock(gRosterData.fLock); 549 assert(lock.IsLocked()); 550 551 int32 count = gRosterData.fCatalogAddOnInfos.CountItems(); 552 for (int32 i = 0; i < count; ++i) { 553 BCatalogAddOnInfo *info 554 = (BCatalogAddOnInfo*)gRosterData.fCatalogAddOnInfos.ItemAt(i); 555 if (info->fName.ICompare(type)!=0 || !info->MakeSureItsLoaded() 556 || !info->fCreateFunc) 557 continue; 558 559 BCatalogAddOn *catalog = info->fCreateFunc(signature, language); 560 if (catalog) { 561 info->fLoadedCatalogs.AddItem(catalog); 562 info->UnloadIfPossible(); 563 return catalog; 564 } 565 } 566 567 return NULL; 568 } 569 570 571 /* 572 * Loads a catalog for the given signature, language and fingerprint. 573 * The request to load this catalog is dispatched to all add-ons in turn, 574 * until an add-on reports success. 575 * If a catalog depends on another language (as 'english-british' depends 576 * on 'english') the dependant catalogs are automatically loaded, too. 577 * So it is perfectly possible that this method returns a catalog-chain 578 * instead of a single catalog. 579 * NULL is returned if no matching catalog could be found. 580 */ 581 BCatalogAddOn* 582 BLocaleRoster::LoadCatalog(const char *signature, const char *language, 583 int32 fingerprint) 584 { 585 if (!signature) 586 return NULL; 587 588 BAutolock lock(gRosterData.fLock); 589 assert(lock.IsLocked()); 590 591 int32 count = gRosterData.fCatalogAddOnInfos.CountItems(); 592 for (int32 i = 0; i < count; ++i) { 593 BCatalogAddOnInfo *info 594 = (BCatalogAddOnInfo*)gRosterData.fCatalogAddOnInfos.ItemAt(i); 595 596 if (!info->MakeSureItsLoaded() || !info->fInstantiateFunc) 597 continue; 598 BMessage languages; 599 if (language) 600 // try to load catalogs for the given language: 601 languages.AddString("language", language); 602 else 603 // try to load catalogs for one of the preferred languages: 604 GetPreferredLanguages(&languages); 605 606 BCatalogAddOn *catalog = NULL; 607 const char *lang; 608 for (int32 l=0; languages.FindString("language", l, &lang)==B_OK; ++l) { 609 catalog = info->fInstantiateFunc(signature, lang, fingerprint); 610 if (catalog) { 611 info->fLoadedCatalogs.AddItem(catalog); 612 #if 0 613 // Chain-loading of catalogs has been disabled, as with the 614 // current way of handling languages (there are no general 615 // languages like 'English', but only specialized ones, like 616 // 'English-american') it does not make sense... 617 // 618 // Chain-load catalogs for languages that depend on 619 // other languages. 620 // The current implementation uses the filename in order to 621 // detect dependencies (parenthood) between languages (it 622 // traverses from "english-british-oxford" to "english-british" 623 // to "english"): 624 int32 pos; 625 BString langName(lang); 626 BCatalogAddOn *currCatalog=catalog, *nextCatalog; 627 while ((pos = langName.FindLast('-')) >= 0) { 628 // language is based on parent, so we load that, too: 629 langName.Truncate(pos); 630 nextCatalog = info->fInstantiateFunc(signature, 631 langName.String(), fingerprint); 632 if (nextCatalog) { 633 info->fLoadedCatalogs.AddItem(nextCatalog); 634 currCatalog->fNext = nextCatalog; 635 currCatalog = nextCatalog; 636 } 637 } 638 #endif 639 return catalog; 640 } 641 } 642 info->UnloadIfPossible(); 643 } 644 645 return NULL; 646 } 647 648 649 /* 650 * Loads an embedded catalog from the given entry-ref (which is usually an 651 * app- or add-on-file. The request to load the catalog is dispatched to all 652 * add-ons in turn, until an add-on reports success. 653 * NULL is returned if no embedded catalog could be found. 654 */ 655 BCatalogAddOn* 656 BLocaleRoster::LoadEmbeddedCatalog(entry_ref *appOrAddOnRef) 657 { 658 if (!appOrAddOnRef) 659 return NULL; 660 661 BAutolock lock(gRosterData.fLock); 662 assert(lock.IsLocked()); 663 664 int32 count = gRosterData.fCatalogAddOnInfos.CountItems(); 665 for (int32 i = 0; i < count; ++i) { 666 BCatalogAddOnInfo *info 667 = (BCatalogAddOnInfo*)gRosterData.fCatalogAddOnInfos.ItemAt(i); 668 669 if (!info->MakeSureItsLoaded() || !info->fInstantiateEmbeddedFunc) 670 continue; 671 672 BCatalogAddOn *catalog = NULL; 673 catalog = info->fInstantiateEmbeddedFunc(appOrAddOnRef); 674 if (catalog) { 675 info->fLoadedCatalogs.AddItem(catalog); 676 return catalog; 677 } 678 info->UnloadIfPossible(); 679 } 680 681 return NULL; 682 } 683 684 685 /* 686 * unloads the given catalog (or rather: catalog-chain). 687 * Every single catalog of the chain will be deleted automatically. 688 * Add-ons that have no more current catalogs are unloaded, too. 689 */ 690 status_t 691 BLocaleRoster::UnloadCatalog(BCatalogAddOn *catalog) 692 { 693 if (!catalog) 694 return B_BAD_VALUE; 695 696 BAutolock lock(gRosterData.fLock); 697 assert(lock.IsLocked()); 698 699 status_t res = B_ERROR; 700 BCatalogAddOn *nextCatalog; 701 // note: as we currently aren't chainloading catalogs, there is only 702 // one catalog to unload... 703 while (catalog) { 704 nextCatalog = catalog->fNext; 705 int32 count = gRosterData.fCatalogAddOnInfos.CountItems(); 706 for (int32 i = 0; i < count; ++i) { 707 BCatalogAddOnInfo *info 708 = static_cast<BCatalogAddOnInfo*>( 709 gRosterData.fCatalogAddOnInfos.ItemAt(i) 710 ); 711 if (info->fLoadedCatalogs.HasItem(catalog)) { 712 info->fLoadedCatalogs.RemoveItem(catalog); 713 delete catalog; 714 info->UnloadIfPossible(); 715 res = B_OK; 716 } 717 } 718 catalog = nextCatalog; 719 } 720 return res; 721 } 722