1 /* 2 * Copyright 2003-2009, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Oliver Tappe, zooey@hirschkaefer.de 7 * Adrien Destugues, pulkomandy@gmail.com 8 */ 9 10 11 #include <memory> 12 #include <new> 13 #include <syslog.h> 14 15 #include <AppFileInfo.h> 16 #include <Application.h> 17 #include <DataIO.h> 18 #include <Directory.h> 19 #include <File.h> 20 #include <FindDirectory.h> 21 #include <fs_attr.h> 22 #include <Message.h> 23 #include <Mime.h> 24 #include <Path.h> 25 #include <Resources.h> 26 #include <Roster.h> 27 28 #include <DefaultCatalog.h> 29 #include <MutableLocaleRoster.h> 30 31 #include <cstdio> 32 33 34 using std::auto_ptr; 35 using std::min; 36 using std::max; 37 using std::pair; 38 39 40 /*! This file implements the default catalog-type for the opentracker locale 41 kit. Alternatively, this could be used as a full add-on, but currently this 42 is provided as part of liblocale.so. 43 */ 44 45 46 static const char *kCatFolder = "catalogs"; 47 static const char *kCatExtension = ".catalog"; 48 49 50 namespace BPrivate { 51 52 53 const char *DefaultCatalog::kCatMimeType 54 = "locale/x-vnd.Be.locale-catalog.default"; 55 56 static int16 kCatArchiveVersion = 1; 57 // version of the catalog archive structure, bump this if you change it! 58 59 const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1; 60 // give highest priority to our embedded catalog-add-on 61 62 63 /*! Constructs a DefaultCatalog with given signature and language and reads 64 the catalog from disk. 65 InitCheck() will be B_OK if catalog could be loaded successfully, it will 66 give an appropriate error-code otherwise. 67 */ 68 DefaultCatalog::DefaultCatalog(const entry_ref &catalogOwner, const char *language, 69 uint32 fingerprint) 70 : 71 HashMapCatalog("", language, fingerprint) 72 { 73 // We created the catalog with an invalid signature, but we fix that now. 74 SetSignature(catalogOwner); 75 status_t status; 76 77 // search for catalog living in sub-folder of app's folder: 78 node_ref nref; 79 nref.device = catalogOwner.device; 80 nref.node = catalogOwner.directory; 81 BDirectory appDir(&nref); 82 BString catalogName("locale/"); 83 catalogName << kCatFolder 84 << "/" << fSignature 85 << "/" << fLanguageName 86 << kCatExtension; 87 BPath catalogPath(&appDir, catalogName.String()); 88 status = ReadFromFile(catalogPath.Path()); 89 90 if (status != B_OK) { 91 // search in data folders 92 93 directory_which which[] = { 94 B_USER_DATA_DIRECTORY, 95 B_COMMON_DATA_DIRECTORY, 96 B_SYSTEM_DATA_DIRECTORY 97 }; 98 99 for (size_t i = 0; i < sizeof(which) / sizeof(which[0]); i++) { 100 BPath path; 101 if (find_directory(which[i], &path) == B_OK) { 102 BString catalogName(path.Path()); 103 catalogName << "/locale/" << kCatFolder 104 << "/" << fSignature 105 << "/" << fLanguageName 106 << kCatExtension; 107 status = ReadFromFile(catalogName.String()); 108 if (status == B_OK) 109 break; 110 } 111 } 112 } 113 114 if (status != B_OK) { 115 // give lowest priority to catalog embedded as resource in application 116 // executable, so they can be overridden easily. 117 status = ReadFromResource(catalogOwner); 118 } 119 120 fInitCheck = status; 121 log_team(LOG_DEBUG, 122 "trying to load default-catalog(sig=%s, lang=%s) results in %s", 123 fSignature.String(), language, strerror(fInitCheck)); 124 } 125 126 127 /*! Constructs a DefaultCatalog and reads it from the resources of the 128 given entry-ref (which usually is an app- or add-on-file). 129 InitCheck() will be B_OK if catalog could be loaded successfully, it will 130 give an appropriate error-code otherwise. 131 */ 132 DefaultCatalog::DefaultCatalog(entry_ref *appOrAddOnRef) 133 : 134 HashMapCatalog("", "", 0) 135 { 136 fInitCheck = ReadFromResource(*appOrAddOnRef); 137 log_team(LOG_DEBUG, 138 "trying to load embedded catalog from resources results in %s", 139 strerror(fInitCheck)); 140 } 141 142 143 /*! Constructs an empty DefaultCatalog with given sig and language. 144 This is used for editing/testing purposes. 145 InitCheck() will always be B_OK. 146 */ 147 DefaultCatalog::DefaultCatalog(const char *path, const char *signature, 148 const char *language) 149 : 150 HashMapCatalog(signature, language, 0), 151 fPath(path) 152 { 153 fInitCheck = B_OK; 154 } 155 156 157 DefaultCatalog::~DefaultCatalog() 158 { 159 } 160 161 162 void 163 DefaultCatalog::SetSignature(const entry_ref &catalogOwner) 164 { 165 // figure out mimetype from image 166 BFile objectFile(&catalogOwner, B_READ_ONLY); 167 BAppFileInfo objectInfo(&objectFile); 168 char objectSignature[B_MIME_TYPE_LENGTH]; 169 if (objectInfo.GetSignature(objectSignature) != B_OK) { 170 log_team(LOG_ERR, "File %s has no mimesignature, so it can't use" 171 " localization.", catalogOwner.name); 172 fSignature = ""; 173 return; 174 } 175 176 // drop supertype from mimetype (should be "application/"): 177 char* stripSignature = objectSignature; 178 while (*stripSignature != '/' && *stripSignature != '\0') 179 stripSignature ++; 180 181 if (*stripSignature == '\0') 182 stripSignature = objectSignature; 183 else 184 stripSignature ++; 185 186 log_team(LOG_DEBUG, "Image %s requested catalog with mimetype %s", 187 catalogOwner.name, stripSignature); 188 fSignature = stripSignature; 189 } 190 191 192 status_t 193 DefaultCatalog::SetRawString(const CatKey& key, const char *translated) 194 { 195 return fCatMap.Put(key, translated); 196 } 197 198 199 status_t 200 DefaultCatalog::ReadFromFile(const char *path) 201 { 202 if (!path) 203 path = fPath.String(); 204 205 BFile catalogFile; 206 status_t res = catalogFile.SetTo(path, B_READ_ONLY); 207 if (res != B_OK) { 208 log_team(LOG_DEBUG, "LocaleKit DefaultCatalog: no catalog at %s", path); 209 return B_ENTRY_NOT_FOUND; 210 } 211 212 fPath = path; 213 log_team(LOG_DEBUG, "LocaleKit DefaultCatalog: found catalog at %s", path); 214 215 off_t sz = 0; 216 res = catalogFile.GetSize(&sz); 217 if (res != B_OK) { 218 log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't get size for " 219 "catalog-file %s", path); 220 return res; 221 } 222 223 auto_ptr<char> buf(new(std::nothrow) char [sz]); 224 if (buf.get() == NULL) { 225 log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't allocate array " 226 "of %d chars", sz); 227 return B_NO_MEMORY; 228 } 229 res = catalogFile.Read(buf.get(), sz); 230 if (res < B_OK) { 231 log_team(LOG_ERR, "LocaleKit DefaultCatalog: couldn't read from " 232 "catalog-file %s", path); 233 return res; 234 } 235 if (res < sz) { 236 log_team(LOG_ERR, 237 "LocaleKit DefaultCatalog: only got %lu instead of %Lu bytes from " 238 "catalog-file %s", res, sz, path); 239 return res; 240 } 241 BMemoryIO memIO(buf.get(), sz); 242 res = Unflatten(&memIO); 243 244 if (res == B_OK) { 245 // some information living in member variables needs to be copied 246 // to attributes. Although these attributes should have been written 247 // when creating the catalog, we make sure that they exist there: 248 UpdateAttributes(catalogFile); 249 } 250 251 return res; 252 } 253 254 255 /* 256 * this method is not currently being used, but it may be useful in the future... 257 */ 258 status_t 259 DefaultCatalog::ReadFromAttribute(const entry_ref &appOrAddOnRef) 260 { 261 BNode node; 262 status_t res = node.SetTo(&appOrAddOnRef); 263 if (res != B_OK) { 264 log_team(LOG_ERR, 265 "couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)", 266 appOrAddOnRef.device, appOrAddOnRef.directory, 267 appOrAddOnRef.name); 268 return B_ENTRY_NOT_FOUND; 269 } 270 271 log_team(LOG_DEBUG, 272 "looking for embedded catalog-attribute in app/add-on" 273 "(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef.device, 274 appOrAddOnRef.directory, appOrAddOnRef.name); 275 276 attr_info attrInfo; 277 res = node.GetAttrInfo(BLocaleRoster::kEmbeddedCatAttr, &attrInfo); 278 if (res != B_OK) { 279 log_team(LOG_DEBUG, "no embedded catalog found"); 280 return B_NAME_NOT_FOUND; 281 } 282 if (attrInfo.type != B_MESSAGE_TYPE) { 283 log_team(LOG_ERR, "attribute %s has incorrect type and is ignored!", 284 BLocaleRoster::kEmbeddedCatAttr); 285 return B_BAD_TYPE; 286 } 287 288 size_t size = attrInfo.size; 289 auto_ptr<char> buf(new(std::nothrow) char [size]); 290 if (buf.get() == NULL) { 291 log_team(LOG_ERR, "couldn't allocate array of %d chars", size); 292 return B_NO_MEMORY; 293 } 294 res = node.ReadAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0, 295 buf.get(), size); 296 if (res < (ssize_t)size) { 297 log_team(LOG_ERR, "unable to read embedded catalog from attribute"); 298 return res < B_OK ? res : B_BAD_DATA; 299 } 300 301 BMemoryIO memIO(buf.get(), size); 302 res = Unflatten(&memIO); 303 304 return res; 305 } 306 307 308 status_t 309 DefaultCatalog::ReadFromResource(const entry_ref &appOrAddOnRef) 310 { 311 BFile file; 312 status_t res = file.SetTo(&appOrAddOnRef, B_READ_ONLY); 313 if (res != B_OK) { 314 log_team(LOG_ERR, 315 "couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)", 316 appOrAddOnRef.device, appOrAddOnRef.directory, 317 appOrAddOnRef.name); 318 return B_ENTRY_NOT_FOUND; 319 } 320 321 log_team(LOG_DEBUG, 322 "looking for embedded catalog-resource in app/add-on" 323 "(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef.device, 324 appOrAddOnRef.directory, appOrAddOnRef.name); 325 326 BResources rsrc; 327 res = rsrc.SetTo(&file); 328 if (res != B_OK) { 329 log_team(LOG_DEBUG, "file has no resources"); 330 return res; 331 } 332 333 int mangledLanguage = CatKey::HashFun(fLanguageName.String(), 0); 334 335 size_t sz; 336 const void *buf = rsrc.LoadResource('CADA', 337 mangledLanguage, &sz); 338 if (!buf) { 339 log_team(LOG_DEBUG, "file has no catalog-resource"); 340 return B_NAME_NOT_FOUND; 341 } 342 343 BMemoryIO memIO(buf, sz); 344 res = Unflatten(&memIO); 345 346 return res; 347 } 348 349 350 status_t 351 DefaultCatalog::WriteToFile(const char *path) 352 { 353 BFile catalogFile; 354 if (path) 355 fPath = path; 356 status_t res = catalogFile.SetTo(fPath.String(), 357 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 358 if (res != B_OK) 359 return res; 360 361 BMallocIO mallocIO; 362 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 363 // set a largish block-size in order to avoid reallocs 364 res = Flatten(&mallocIO); 365 if (res == B_OK) { 366 ssize_t wsz; 367 wsz = catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength()); 368 if (wsz != (ssize_t)mallocIO.BufferLength()) 369 return B_FILE_ERROR; 370 371 // set mimetype-, language- and signature-attributes: 372 UpdateAttributes(catalogFile); 373 } 374 if (res == B_OK) 375 UpdateAttributes(catalogFile); 376 return res; 377 } 378 379 380 /* 381 * this method is not currently being used, but it may be useful in the 382 * future... 383 */ 384 status_t 385 DefaultCatalog::WriteToAttribute(const entry_ref &appOrAddOnRef) 386 { 387 BNode node; 388 status_t res = node.SetTo(&appOrAddOnRef); 389 if (res != B_OK) 390 return res; 391 392 BMallocIO mallocIO; 393 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 394 // set a largish block-size in order to avoid reallocs 395 res = Flatten(&mallocIO); 396 397 if (res == B_OK) { 398 ssize_t wsz; 399 wsz = node.WriteAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0, 400 mallocIO.Buffer(), mallocIO.BufferLength()); 401 if (wsz < B_OK) 402 res = wsz; 403 else if (wsz != (ssize_t)mallocIO.BufferLength()) 404 res = B_ERROR; 405 } 406 return res; 407 } 408 409 410 status_t 411 DefaultCatalog::WriteToResource(const entry_ref &appOrAddOnRef) 412 { 413 BFile file; 414 status_t res = file.SetTo(&appOrAddOnRef, B_READ_WRITE); 415 if (res != B_OK) 416 return res; 417 418 BResources rsrc; 419 res = rsrc.SetTo(&file); 420 if (res != B_OK) 421 return res; 422 423 BMallocIO mallocIO; 424 mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L)); 425 // set a largish block-size in order to avoid reallocs 426 res = Flatten(&mallocIO); 427 428 int mangledLanguage = CatKey::HashFun(fLanguageName.String(), 0); 429 430 if (res == B_OK) { 431 res = rsrc.AddResource('CADA', mangledLanguage, 432 mallocIO.Buffer(), mallocIO.BufferLength(), 433 BString(fLanguageName) << " catalog"); 434 } 435 436 return res; 437 } 438 439 440 /*! Writes mimetype, language-name and signature of catalog into the 441 catalog-file. 442 */ 443 void 444 DefaultCatalog::UpdateAttributes(BFile& catalogFile) 445 { 446 static const int bufSize = 256; 447 char buf[bufSize]; 448 uint32 temp; 449 if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, 450 bufSize) <= 0 451 || strcmp(kCatMimeType, buf) != 0) { 452 catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, 453 kCatMimeType, strlen(kCatMimeType)+1); 454 } 455 if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 456 &buf, bufSize) <= 0 457 || fLanguageName != buf) { 458 catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 459 fLanguageName.String(), fLanguageName.Length()+1); 460 } 461 if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 462 &buf, bufSize) <= 0 463 || fSignature != buf) { 464 catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 465 fSignature.String(), fSignature.Length()+1); 466 } 467 if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 468 0, &temp, sizeof(uint32)) <= 0) { 469 catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 470 0, &fFingerprint, sizeof(uint32)); 471 } 472 } 473 474 475 status_t 476 DefaultCatalog::Flatten(BDataIO *dataIO) 477 { 478 UpdateFingerprint(); 479 // make sure we have the correct fingerprint before we flatten it 480 481 status_t res; 482 BMessage archive; 483 int32 count = fCatMap.Size(); 484 res = archive.AddString("class", "DefaultCatalog"); 485 if (res == B_OK) 486 res = archive.AddInt32("c:sz", count); 487 if (res == B_OK) 488 res = archive.AddInt16("c:ver", kCatArchiveVersion); 489 if (res == B_OK) 490 res = archive.AddString("c:lang", fLanguageName.String()); 491 if (res == B_OK) 492 res = archive.AddString("c:sig", fSignature.String()); 493 if (res == B_OK) 494 res = archive.AddInt32("c:fpr", fFingerprint); 495 if (res == B_OK) 496 res = archive.Flatten(dataIO); 497 498 CatMap::Iterator iter = fCatMap.GetIterator(); 499 CatMap::Entry entry; 500 while (res == B_OK && iter.HasNext()) { 501 entry = iter.Next(); 502 archive.MakeEmpty(); 503 res = archive.AddString("c:ostr", entry.key.fString.String()); 504 if (res == B_OK) 505 res = archive.AddString("c:ctxt", entry.key.fContext.String()); 506 if (res == B_OK) 507 res = archive.AddString("c:comt", entry.key.fComment.String()); 508 if (res == B_OK) 509 res = archive.AddInt32("c:hash", entry.key.fHashVal); 510 if (res == B_OK) 511 res = archive.AddString("c:tstr", entry.value.String()); 512 if (res == B_OK) 513 res = archive.Flatten(dataIO); 514 } 515 516 return res; 517 } 518 519 520 status_t 521 DefaultCatalog::Unflatten(BDataIO *dataIO) 522 { 523 fCatMap.Clear(); 524 int32 count = 0; 525 int16 version; 526 BMessage archiveMsg; 527 status_t res = archiveMsg.Unflatten(dataIO); 528 529 if (res == B_OK) { 530 res = archiveMsg.FindInt16("c:ver", &version) 531 || archiveMsg.FindInt32("c:sz", &count); 532 } 533 if (res == B_OK) { 534 fLanguageName = archiveMsg.FindString("c:lang"); 535 fSignature = archiveMsg.FindString("c:sig"); 536 uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr"); 537 538 // if a specific fingerprint has been requested and the catalog does in 539 // fact have a fingerprint, both are compared. If they mismatch, we do 540 // not accept this catalog: 541 if (foundFingerprint != 0 && fFingerprint != 0 542 && foundFingerprint != fFingerprint) { 543 log_team(LOG_INFO, "default-catalog(sig=%s, lang=%s) " 544 "has mismatching fingerprint (%ld instead of the requested %ld), " 545 "so this catalog is skipped.", 546 fSignature.String(), fLanguageName.String(), foundFingerprint, 547 fFingerprint); 548 res = B_MISMATCHED_VALUES; 549 } else 550 fFingerprint = foundFingerprint; 551 } 552 553 if (res == B_OK && count > 0) { 554 CatKey key; 555 const char *keyStr; 556 const char *keyCtx; 557 const char *keyCmt; 558 const char *translated; 559 560 // fCatMap.resize(count); 561 // There is no resize method in Haiku's HashMap to preallocate 562 // memory. 563 for (int i=0; res == B_OK && i < count; ++i) { 564 res = archiveMsg.Unflatten(dataIO); 565 if (res == B_OK) 566 res = archiveMsg.FindString("c:ostr", &keyStr); 567 if (res == B_OK) 568 res = archiveMsg.FindString("c:ctxt", &keyCtx); 569 if (res == B_OK) 570 res = archiveMsg.FindString("c:comt", &keyCmt); 571 if (res == B_OK) 572 res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal); 573 if (res == B_OK) 574 res = archiveMsg.FindString("c:tstr", &translated); 575 if (res == B_OK) { 576 key.fString = keyStr; 577 key.fContext = keyCtx; 578 key.fComment = keyCmt; 579 fCatMap.Put(key, translated); 580 } 581 } 582 uint32 checkFP = ComputeFingerprint(); 583 if (fFingerprint != checkFP) { 584 log_team(LOG_WARNING, "default-catalog(sig=%s, lang=%s) " 585 "has wrong fingerprint after load (%ld instead of the %ld). " 586 "The catalog data may be corrupted, so this catalog is skipped.", 587 fSignature.String(), fLanguageName.String(), checkFP, 588 fFingerprint); 589 return B_BAD_DATA; 590 } 591 } 592 return res; 593 } 594 595 596 BCatalogData * 597 DefaultCatalog::Instantiate(const entry_ref &catalogOwner, const char *language, 598 uint32 fingerprint) 599 { 600 DefaultCatalog *catalog 601 = new(std::nothrow) DefaultCatalog(catalogOwner, language, fingerprint); 602 if (catalog && catalog->InitCheck() != B_OK) { 603 delete catalog; 604 return NULL; 605 } 606 return catalog; 607 } 608 609 610 BCatalogData * 611 DefaultCatalog::Create(const char *signature, const char *language) 612 { 613 DefaultCatalog *catalog 614 = new(std::nothrow) DefaultCatalog("", signature, language); 615 if (catalog && catalog->InitCheck() != B_OK) { 616 delete catalog; 617 return NULL; 618 } 619 return catalog; 620 } 621 622 623 } // namespace BPrivate 624 625 626 extern "C" status_t 627 default_catalog_get_available_languages(BMessage* availableLanguages, 628 const char* sigPattern, const char* langPattern, int32 fingerprint) 629 { 630 if (availableLanguages == NULL || sigPattern == NULL) 631 return B_BAD_DATA; 632 633 app_info appInfo; 634 be_app->GetAppInfo(&appInfo); 635 node_ref nref; 636 nref.device = appInfo.ref.device; 637 nref.node = appInfo.ref.directory; 638 BDirectory appDir(&nref); 639 BString catalogName("locale/"); 640 catalogName << kCatFolder 641 << "/" << sigPattern ; 642 BPath catalogPath(&appDir, catalogName.String()); 643 BEntry file(catalogPath.Path()); 644 BDirectory dir(&file); 645 646 char fileName[B_FILE_NAME_LENGTH]; 647 while(dir.GetNextEntry(&file) == B_OK) { 648 file.GetName(fileName); 649 BString langName(fileName); 650 langName.Replace(kCatExtension, "", 1); 651 availableLanguages->AddString("language", langName); 652 } 653 654 // search in data folders 655 656 directory_which which[] = { 657 B_USER_DATA_DIRECTORY, 658 B_COMMON_DATA_DIRECTORY, 659 B_SYSTEM_DATA_DIRECTORY 660 }; 661 662 for (size_t i = 0; i < sizeof(which) / sizeof(which[0]); i++) { 663 BPath path; 664 if (find_directory(which[i], &path) == B_OK) { 665 catalogName = BString("locale/") 666 << kCatFolder 667 << "/" << sigPattern; 668 669 BPath catalogPath(path.Path(), catalogName.String()); 670 BEntry file(catalogPath.Path()); 671 BDirectory dir(&file); 672 673 char fileName[B_FILE_NAME_LENGTH]; 674 while(dir.GetNextEntry(&file) == B_OK) { 675 file.GetName(fileName); 676 BString langName(fileName); 677 langName.Replace(kCatExtension, "", 1); 678 availableLanguages->AddString("language", langName); 679 } 680 } 681 } 682 683 return B_OK; 684 } 685