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