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