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