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