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