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