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