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
escapeQuotedChars(BString & stringToEscape)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 */
PlainTextCatalog(const entry_ref & owner,const char * language,uint32 fingerprint)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 */
PlainTextCatalog(const char * path,const char * signature,const char * language)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
~PlainTextCatalog()146 PlainTextCatalog::~PlainTextCatalog()
147 {
148 }
149
150
151 void
SetSignature(const entry_ref & catalogOwner)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
ReadFromFile(const char * path)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
WriteToFile(const char * path)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
UpdateAttributes(BFile & catalogFile)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
UpdateAttributes(const char * path)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 *
Instantiate(const entry_ref & owner,const char * language,uint32 fingerprint)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 *
instantiate_catalog(const entry_ref & owner,const char * language,uint32 fingerprint)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 *
create_catalog(const char * signature,const char * language)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
get_available_languages(BMessage * availableLanguages,const char * sigPattern=NULL,const char * langPattern=NULL,int32 fingerprint=0)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