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 <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::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 HashMapCatalog(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 } 127 128 129 /* 130 * constructs an empty PlainTextCatalog with given sig and language. 131 * This is used for editing/testing purposes. 132 * InitCheck() will always be B_OK. 133 */ 134 PlainTextCatalog::PlainTextCatalog(const char *path, const char *signature, 135 const char *language) 136 : 137 HashMapCatalog(signature, language, 0), 138 fPath(path) 139 { 140 fInitCheck = B_OK; 141 } 142 143 144 PlainTextCatalog::~PlainTextCatalog() 145 { 146 } 147 148 149 status_t 150 PlainTextCatalog::ReadFromFile(const char *path) 151 { 152 std::fstream catalogFile; 153 std::string currentItem; 154 155 if (!path) 156 path = fPath.String(); 157 158 catalogFile.open(path, std::fstream::in); 159 if (!catalogFile.is_open()) 160 return B_ENTRY_NOT_FOUND; 161 162 // Now read all the data from the file 163 164 // The first line holds some info about the catalog : 165 // ArchiveVersion \t LanguageName \t Signature \t FingerPrint 166 if (std::getline(catalogFile, currentItem, '\t').good()) { 167 // Get the archive version 168 int arcver= -1; 169 std::istringstream ss(currentItem); 170 ss >> arcver; 171 if (ss.fail()) { 172 // can't convert to int 173 return B_ERROR; 174 } 175 176 if (arcver != kCatArchiveVersion) { 177 // wrong version 178 return B_ERROR; 179 } 180 } else 181 return B_ERROR; 182 183 if (std::getline(catalogFile, currentItem, '\t').good()) { 184 // Get the language 185 fLanguageName = currentItem.c_str() ; 186 } else 187 return B_ERROR; 188 189 if (std::getline(catalogFile, currentItem, '\t').good()) { 190 // Get the signature 191 fSignature = currentItem.c_str() ; 192 } else 193 return B_ERROR; 194 195 if (std::getline(catalogFile, currentItem).good()) { 196 // Get the fingerprint 197 std::istringstream ss(currentItem); 198 uint32 foundFingerprint; 199 ss >> foundFingerprint; 200 if (ss.fail()) 201 return B_ERROR; 202 203 if (fFingerprint == 0) 204 fFingerprint = foundFingerprint; 205 206 if (fFingerprint != foundFingerprint) { 207 return B_MISMATCHED_VALUES; 208 } 209 } else 210 return B_ERROR; 211 212 // We managed to open the file, so we remember it's the one we are using 213 fPath = path; 214 215 std::string originalString; 216 std::string context; 217 std::string comment; 218 std::string translated; 219 220 while (std::getline(catalogFile, originalString,'\t').good()) { 221 // Each line is : "original string \t context \t comment \t translation" 222 223 if (!std::getline(catalogFile, context,'\t').good()) 224 return B_ERROR; 225 226 if (!std::getline(catalogFile, comment,'\t').good()) 227 return B_ERROR; 228 229 if (!std::getline(catalogFile, translated).good()) 230 return B_ERROR; 231 232 // We could do that : 233 // SetString(key, translated.c_str()); 234 // but we want to keep the strings in the new catkey. Hash collisions 235 // happen, you know. (and CatKey::== will fail) 236 SetString(originalString.c_str(), translated.c_str(), context.c_str(), 237 comment.c_str()); 238 } 239 240 catalogFile.close(); 241 242 uint32 checkFP = ComputeFingerprint(); 243 if (fFingerprint != checkFP) 244 return B_BAD_DATA; 245 246 // some information living in member variables needs to be copied 247 // to attributes. Although these attributes should have been written 248 // when creating the catalog, we make sure that they exist there: 249 UpdateAttributes(path); 250 return B_OK; 251 } 252 253 254 status_t 255 PlainTextCatalog::WriteToFile(const char *path) 256 { 257 BString textContent; 258 BFile catalogFile; 259 260 if (path) 261 fPath = path; 262 status_t res = catalogFile.SetTo(fPath.String(), 263 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 264 if (res != B_OK) 265 return res; 266 267 UpdateFingerprint(); 268 // make sure we have the correct fingerprint before we flatten it 269 270 textContent << kCatArchiveVersion << "\t" << fLanguageName.String() << "\t" 271 << fSignature.String() << "\t" << fFingerprint << "\n"; 272 273 res = catalogFile.Write(textContent.String(),textContent.Length()); 274 if (res != textContent.Length()) 275 return res; 276 277 CatMap::Iterator iter = fCatMap.GetIterator(); 278 CatMap::Entry entry; 279 BString original; 280 BString comment; 281 BString translated; 282 283 while (iter.HasNext()) { 284 entry = iter.Next(); 285 original = entry.key.fString; 286 comment = entry.key.fComment; 287 translated = entry.value; 288 289 escapeQuotedChars(original); 290 escapeQuotedChars(comment); 291 escapeQuotedChars(translated); 292 293 textContent.Truncate(0); 294 textContent << original.String() << "\t" 295 << entry.key.fContext.String() << "\t" 296 << comment << "\t" 297 << translated.String() << "\n"; 298 res = catalogFile.Write(textContent.String(),textContent.Length()); 299 if (res != textContent.Length()) 300 return res; 301 } 302 303 // set mimetype-, language- and signature-attributes: 304 UpdateAttributes(catalogFile); 305 306 return B_OK; 307 } 308 309 310 /* 311 * writes mimetype, language-name and signature of catalog into the 312 * catalog-file. 313 */ 314 void 315 PlainTextCatalog::UpdateAttributes(BFile& catalogFile) 316 { 317 static const int bufSize = 256; 318 char buf[bufSize]; 319 uint32 temp; 320 if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, bufSize) 321 <= 0 322 || strcmp(kCatMimeType, buf) != 0) { 323 catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, 324 kCatMimeType, strlen(kCatMimeType)+1); 325 } 326 if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 327 &buf, bufSize) <= 0 328 || fLanguageName != buf) { 329 catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0, 330 fLanguageName.String(), fLanguageName.Length()+1); 331 } 332 if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 333 &buf, bufSize) <= 0 334 || fSignature != buf) { 335 catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0, 336 fSignature.String(), fSignature.Length()+1); 337 } 338 if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 339 0, &temp, sizeof(uint32)) <= 0) { 340 catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE, 341 0, &fFingerprint, sizeof(uint32)); 342 } 343 } 344 345 346 void 347 PlainTextCatalog::UpdateAttributes(const char* path) 348 { 349 BEntry entry(path); 350 BFile node(&entry, B_READ_WRITE); 351 UpdateAttributes(node); 352 } 353 354 355 BCatalogData * 356 PlainTextCatalog::Instantiate(const char *signature, const char *language, 357 uint32 fingerprint) 358 { 359 PlainTextCatalog *catalog 360 = new(std::nothrow) PlainTextCatalog(signature, language, fingerprint); 361 if (catalog && catalog->InitCheck() != B_OK) { 362 delete catalog; 363 return NULL; 364 } 365 return catalog; 366 } 367 368 369 extern "C" BCatalogData * 370 instantiate_catalog(const char *signature, const char *language, 371 uint32 fingerprint) 372 { 373 PlainTextCatalog *catalog 374 = new(std::nothrow) PlainTextCatalog(signature, language, fingerprint); 375 if (catalog && catalog->InitCheck() != B_OK) { 376 delete catalog; 377 return NULL; 378 } 379 return catalog; 380 } 381 382 383 extern "C" BCatalogData * 384 create_catalog(const char *signature, const char *language) 385 { 386 PlainTextCatalog *catalog 387 = new(std::nothrow) PlainTextCatalog("emptycat", signature, language); 388 return catalog; 389 } 390 391 392 extern "C" status_t 393 get_available_languages(BMessage* availableLanguages, 394 const char* sigPattern = NULL, const char* langPattern = NULL, 395 int32 fingerprint = 0) 396 { 397 // TODO 398 return B_ERROR; 399 } 400 401 402 uint8 gCatalogAddOnPriority = 99; 403 // give low priority to this add on, we don't want it to be actually used 404