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