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