1 /* 2 ** Copyright 2009 Adrien Destugues, pulkomandy@gmail.com. All rights reserved. 3 ** Distributed under the terms of the MIT License. 4 */ 5 6 #include <PlainTextCatalog.h> 7 8 #include <assert.h> 9 #include <cstdio> 10 #include <iostream> 11 #include <fstream> 12 #include <memory> 13 #include <new> 14 #include <sstream> 15 #include <string> 16 17 #include <AppFileInfo.h> 18 #include <Application.h> 19 #include <Directory.h> 20 #include <File.h> 21 #include <FindDirectory.h> 22 #include <fs_attr.h> 23 #include <Message.h> 24 #include <Mime.h> 25 #include <Path.h> 26 #include <Resources.h> 27 #include <Roster.h> 28 #include <String.h> 29 30 #include <LocaleRoster.h> 31 #include <Catalog.h> 32 33 34 using BPrivate::HashMapCatalog; 35 using BPrivate::PlainTextCatalog; 36 using std::auto_ptr; 37 using std::min; 38 using std::max; 39 using std::pair; 40 41 42 /* 43 * This file implements the plain text catalog-type for the Haiku 44 * locale kit. It is not meant to be used in applications like other add ons, 45 * but only as a way to get an easy to translate file for developers. 46 */ 47 48 49 extern "C" uint32 adler32(uint32 adler, const uint8 *buf, uint32 len); 50 // definition lives in adler32.c 51 52 static const char *kCatFolder = "catalogs"; 53 static const char *kCatExtension = ".catkeys"; 54 55 const char *PlainTextCatalog::kCatMimeType 56 = "locale/x-vnd.Be.locale-catalog.plaintext"; 57 58 static int16 kCatArchiveVersion = 1; 59 // version of the catalog archive structure, bump this if you change it! 60 61 62 void 63 escapeQuotedChars(BString& stringToEscape) 64 { 65 stringToEscape.ReplaceAll("\\","\\\\"); 66 stringToEscape.ReplaceAll("\n","\\n"); 67 stringToEscape.ReplaceAll("\t","\\t"); 68 stringToEscape.ReplaceAll("\"","\\\""); 69 } 70 71 72 /* 73 * constructs a PlainTextCatalog with given signature and language and reads 74 * the catalog from disk. 75 * InitCheck() will be B_OK if catalog could be loaded successfully, it will 76 * give an appropriate error-code otherwise. 77 */ 78 PlainTextCatalog::PlainTextCatalog(const entry_ref &owner, const char *language, 79 uint32 fingerprint) 80 : 81 HashMapCatalog("", language, fingerprint) 82 { 83 // We created the catalog with an invalid signature, but we fix that now. 84 SetSignature(owner); 85 86 // give highest priority to catalog living in sub-folder of app's folder: 87 app_info appInfo; 88 be_app->GetAppInfo(&appInfo); 89 node_ref nref; 90 nref.device = appInfo.ref.device; 91 nref.node = appInfo.ref.directory; 92 BDirectory appDir(&nref); 93 BString catalogName("locale/"); 94 catalogName << kCatFolder 95 << "/" << fSignature 96 << "/" << fLanguageName 97 << kCatExtension; 98 BPath catalogPath(&appDir, catalogName.String()); 99 status_t status = ReadFromFile(catalogPath.Path()); 100 101 if (status != B_OK) { 102 // look in common-etc folder (/boot/home/config/etc): 103 BPath commonEtcPath; 104 find_directory(B_SYSTEM_ETC_DIRECTORY, &commonEtcPath); 105 if (commonEtcPath.InitCheck() == B_OK) { 106 catalogName = BString(commonEtcPath.Path()) 107 << "/locale/" << kCatFolder 108 << "/" << fSignature 109 << "/" << fLanguageName 110 << kCatExtension; 111 status = ReadFromFile(catalogName.String()); 112 } 113 } 114 115 if (status != B_OK) { 116 // look in system-etc folder (/boot/beos/etc): 117 BPath systemEtcPath; 118 find_directory(B_BEOS_ETC_DIRECTORY, &systemEtcPath); 119 if (systemEtcPath.InitCheck() == B_OK) { 120 catalogName = BString(systemEtcPath.Path()) 121 << "/locale/" << kCatFolder 122 << "/" << fSignature 123 << "/" << fLanguageName 124 << kCatExtension; 125 status = ReadFromFile(catalogName.String()); 126 } 127 } 128 129 fInitCheck = status; 130 } 131 132 133 /* 134 * constructs an empty PlainTextCatalog with given sig and language. 135 * This is used for editing/testing purposes. 136 * InitCheck() will always be B_OK. 137 */ 138 PlainTextCatalog::PlainTextCatalog(const char *path, const char *signature, 139 const char *language) 140 : 141 HashMapCatalog(signature, language, 0), 142 fPath(path) 143 { 144 fInitCheck = B_OK; 145 } 146 147 148 PlainTextCatalog::~PlainTextCatalog() 149 { 150 } 151 152 153 void 154 PlainTextCatalog::SetSignature(const entry_ref &catalogOwner) 155 { 156 // figure out mimetype from image 157 BFile objectFile(&catalogOwner, B_READ_ONLY); 158 BAppFileInfo objectInfo(&objectFile); 159 char objectSignature[B_MIME_TYPE_LENGTH]; 160 if (objectInfo.GetSignature(objectSignature) != B_OK) { 161 fSignature = ""; 162 return; 163 } 164 165 // drop supertype from mimetype (should be "application/"): 166 char* stripSignature = objectSignature; 167 while (*stripSignature != '/' && *stripSignature != '\0') 168 stripSignature ++; 169 170 if (*stripSignature == '\0') 171 stripSignature = objectSignature; 172 else 173 stripSignature ++; 174 175 fSignature = stripSignature; 176 } 177 178 179 status_t 180 PlainTextCatalog::ReadFromFile(const char *path) 181 { 182 std::fstream catalogFile; 183 std::string currentItem; 184 185 if (!path) 186 path = fPath.String(); 187 188 catalogFile.open(path, std::fstream::in); 189 if (!catalogFile.is_open()) 190 return B_ENTRY_NOT_FOUND; 191 192 // Now read all the data from the file 193 194 // The first line holds some info about the catalog : 195 // ArchiveVersion \t LanguageName \t Signature \t FingerPrint 196 if (std::getline(catalogFile, currentItem, '\t').good()) { 197 // Get the archive version 198 int arcver= -1; 199 std::istringstream ss(currentItem); 200 ss >> arcver; 201 if (ss.fail()) { 202 // can't convert to int 203 return B_ERROR; 204 } 205 206 if (arcver != kCatArchiveVersion) { 207 // wrong version 208 return B_ERROR; 209 } 210 } else 211 return B_ERROR; 212 213 if (std::getline(catalogFile, currentItem, '\t').good()) { 214 // Get the language 215 fLanguageName = currentItem.c_str() ; 216 } else 217 return B_ERROR; 218 219 if (std::getline(catalogFile, currentItem, '\t').good()) { 220 // Get the signature 221 fSignature = currentItem.c_str() ; 222 } else 223 return B_ERROR; 224 225 if (std::getline(catalogFile, currentItem).good()) { 226 // Get the fingerprint 227 std::istringstream ss(currentItem); 228 uint32 foundFingerprint; 229 ss >> foundFingerprint; 230 if (ss.fail()) 231 return B_ERROR; 232 233 if (fFingerprint == 0) 234 fFingerprint = foundFingerprint; 235 236 if (fFingerprint != foundFingerprint) { 237 return B_MISMATCHED_VALUES; 238 } 239 } else 240 return B_ERROR; 241 242 // We managed to open the file, so we remember it's the one we are using 243 fPath = path; 244 245 std::string originalString; 246 std::string context; 247 std::string comment; 248 std::string translated; 249 250 while (std::getline(catalogFile, originalString,'\t').good()) { 251 // Each line is : "original string \t context \t comment \t translation" 252 253 if (!std::getline(catalogFile, context,'\t').good()) 254 return B_ERROR; 255 256 if (!std::getline(catalogFile, comment,'\t').good()) 257 return B_ERROR; 258 259 if (!std::getline(catalogFile, translated).good()) 260 return B_ERROR; 261 262 // We could do that : 263 // SetString(key, translated.c_str()); 264 // but we want to keep the strings in the new catkey. Hash collisions 265 // happen, you know. (and CatKey::== will fail) 266 SetString(originalString.c_str(), translated.c_str(), context.c_str(), 267 comment.c_str()); 268 } 269 270 catalogFile.close(); 271 272 uint32 checkFP = ComputeFingerprint(); 273 if (fFingerprint != checkFP) 274 return B_BAD_DATA; 275 276 // some information living in member variables needs to be copied 277 // to attributes. Although these attributes should have been written 278 // when creating the catalog, we make sure that they exist there: 279 UpdateAttributes(path); 280 return B_OK; 281 } 282 283 284 status_t 285 PlainTextCatalog::WriteToFile(const char *path) 286 { 287 BString textContent; 288 BFile catalogFile; 289 290 if (path) 291 fPath = path; 292 status_t res = catalogFile.SetTo(fPath.String(), 293 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 294 if (res != B_OK) 295 return res; 296 297 UpdateFingerprint(); 298 // make sure we have the correct fingerprint before we flatten it 299 300 textContent << kCatArchiveVersion << "\t" << fLanguageName.String() << "\t" 301 << fSignature.String() << "\t" << fFingerprint << "\n"; 302 303 res = catalogFile.Write(textContent.String(),textContent.Length()); 304 if (res != textContent.Length()) 305 return res; 306 307 CatMap::Iterator iter = fCatMap.GetIterator(); 308 CatMap::Entry entry; 309 BString original; 310 BString comment; 311 BString translated; 312 313 while (iter.HasNext()) { 314 entry = iter.Next(); 315 original = entry.key.fString; 316 comment = entry.key.fComment; 317 translated = entry.value; 318 319 escapeQuotedChars(original); 320 escapeQuotedChars(comment); 321 escapeQuotedChars(translated); 322 323 textContent.Truncate(0); 324 textContent << original.String() << "\t" 325 << entry.key.fContext.String() << "\t" 326 << comment << "\t" 327 << translated.String() << "\n"; 328 res = catalogFile.Write(textContent.String(),textContent.Length()); 329 if (res != textContent.Length()) 330 return res; 331 } 332 333 // set mimetype-, language- and signature-attributes: 334 UpdateAttributes(catalogFile); 335 336 return B_OK; 337 } 338 339 340 /* 341 * writes mimetype, language-name and signature of catalog into the 342 * catalog-file. 343 */ 344 void 345 PlainTextCatalog::UpdateAttributes(BFile& catalogFile) 346 { 347 static const int bufSize = 256; 348 char buf[bufSize]; 349 uint32 temp; 350 if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, bufSize) 351 <= 0 352 || strcmp(kCatMimeType, buf) != 0) { 353 catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, 354 kCatMimeType, strlen(kCatMimeType)+1); 355 } 356 if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 357 &buf, bufSize) <= 0 358 || fLanguageName != buf) { 359 catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 360 fLanguageName.String(), fLanguageName.Length()+1); 361 } 362 if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 363 &buf, bufSize) <= 0 364 || fSignature != buf) { 365 catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 366 fSignature.String(), fSignature.Length()+1); 367 } 368 if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 369 0, &temp, sizeof(uint32)) <= 0) { 370 catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 371 0, &fFingerprint, sizeof(uint32)); 372 } 373 } 374 375 376 void 377 PlainTextCatalog::UpdateAttributes(const char* path) 378 { 379 BEntry entry(path); 380 BFile node(&entry, B_READ_WRITE); 381 UpdateAttributes(node); 382 } 383 384 385 BCatalogData * 386 PlainTextCatalog::Instantiate(const entry_ref& owner, const char *language, 387 uint32 fingerprint) 388 { 389 PlainTextCatalog *catalog 390 = new(std::nothrow) PlainTextCatalog(owner, language, fingerprint); 391 if (catalog && catalog->InitCheck() != B_OK) { 392 delete catalog; 393 return NULL; 394 } 395 return catalog; 396 } 397 398 399 extern "C" BCatalogData * 400 instantiate_catalog(const entry_ref& owner, const char *language, 401 uint32 fingerprint) 402 { 403 PlainTextCatalog *catalog 404 = new(std::nothrow) PlainTextCatalog(owner, language, fingerprint); 405 if (catalog && catalog->InitCheck() != B_OK) { 406 delete catalog; 407 return NULL; 408 } 409 return catalog; 410 } 411 412 413 extern "C" BCatalogData * 414 create_catalog(const char *signature, const char *language) 415 { 416 PlainTextCatalog *catalog 417 = new(std::nothrow) PlainTextCatalog("emptycat", signature, language); 418 return catalog; 419 } 420 421 422 extern "C" status_t 423 get_available_languages(BMessage* availableLanguages, 424 const char* sigPattern = NULL, const char* langPattern = NULL, 425 int32 fingerprint = 0) 426 { 427 // TODO 428 return B_ERROR; 429 } 430 431 432 uint8 gCatalogAddOnPriority = 99; 433 // give low priority to this add on, we don't want it to be actually used 434