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