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