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