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