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