xref: /haiku/src/add-ons/locale/catalogs/plaintext/Catalog.cpp (revision 75f152215517c2af1a93938f46d3156503329a5e)
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 = ".txt";
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("\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 char *signature, const char *language,
77 	uint32 fingerprint)
78 	:
79 	BHashMapCatalog(signature, language, fingerprint)
80 {
81 	// give highest priority to catalog living in sub-folder of app's folder:
82 	app_info appInfo;
83 	be_app->GetAppInfo(&appInfo);
84 	node_ref nref;
85 	nref.device = appInfo.ref.device;
86 	nref.node = appInfo.ref.directory;
87 	BDirectory appDir(&nref);
88 	BString catalogName("locale/");
89 	catalogName << kCatFolder
90 		<< "/" << fSignature
91 		<< "/" << fLanguageName
92 		<< kCatExtension;
93 	BPath catalogPath(&appDir, catalogName.String());
94 	status_t status = ReadFromFile(catalogPath.Path());
95 
96 	if (status != B_OK) {
97 		// look in common-etc folder (/boot/home/config/etc):
98 		BPath commonEtcPath;
99 		find_directory(B_COMMON_ETC_DIRECTORY, &commonEtcPath);
100 		if (commonEtcPath.InitCheck() == B_OK) {
101 			catalogName = BString(commonEtcPath.Path())
102 							<< "/locale/" << kCatFolder
103 							<< "/" << fSignature
104 							<< "/" << fLanguageName
105 							<< kCatExtension;
106 			status = ReadFromFile(catalogName.String());
107 		}
108 	}
109 
110 	if (status != B_OK) {
111 		// look in system-etc folder (/boot/beos/etc):
112 		BPath systemEtcPath;
113 		find_directory(B_BEOS_ETC_DIRECTORY, &systemEtcPath);
114 		if (systemEtcPath.InitCheck() == B_OK) {
115 			catalogName = BString(systemEtcPath.Path())
116 							<< "/locale/" << kCatFolder
117 							<< "/" << fSignature
118 							<< "/" << fLanguageName
119 							<< kCatExtension;
120 			status = ReadFromFile(catalogName.String());
121 		}
122 	}
123 
124 	fInitCheck = status;
125 	log_team(LOG_DEBUG,
126 		"trying to load default-catalog(sig=%s, lang=%s) results in %s",
127 		signature, language, strerror(fInitCheck));
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 	BHashMapCatalog(signature, language, 0),
140 	fPath(path)
141 {
142 	fInitCheck = B_OK;
143 }
144 
145 
146 PlainTextCatalog::~PlainTextCatalog()
147 {
148 }
149 
150 
151 status_t
152 PlainTextCatalog::ReadFromFile(const char *path)
153 {
154 	std::fstream catalogFile;
155 	std::string  currentItem;
156 
157 	if (!path)
158 		path = fPath.String();
159 
160 	catalogFile.open(path, std::fstream::in);
161 	if (!catalogFile.is_open()) {
162 		log_team(LOG_DEBUG, "couldn't open catalog at %s", path);
163 		return B_ENTRY_NOT_FOUND;
164 	}
165 
166 	// Now read all the data from the file
167 
168 	// The first line holds some info about the catalog :
169 	// ArchiveVersion \t LanguageName \t Signature \t FingerPrint
170 	if (std::getline(catalogFile, currentItem, '\t').good()) {
171 		// Get the archive version
172 		int arcver= -1;
173 		std::istringstream ss(currentItem);
174 		ss >> arcver;
175 		if (ss.fail()) {
176     		// can't convert to int
177 			log_team(LOG_DEBUG,
178 				"Unable to extract archive version ( string: %s ) from %s",
179 				currentItem.c_str(), path);
180 			return B_ERROR;
181 		}
182 
183 		if (arcver != kCatArchiveVersion) {
184     		// wrong version
185 			log_team(LOG_DEBUG,
186 				"Wrong archive version ! Got %d instead of %d from %s", arcver,
187 				kCatArchiveVersion, path);
188 			return B_ERROR;
189 		}
190 	} else {
191 		log_team(LOG_DEBUG, "Unable to read from catalog %s", path);
192 		return B_ERROR;
193 	}
194 
195 	if (std::getline(catalogFile, currentItem, '\t').good()) {
196 		// Get the language
197 		fLanguageName << currentItem.c_str() ;
198 	} else {
199 		log_team(LOG_DEBUG, "Unable to get language from %s", path);
200 		return B_ERROR;
201 	}
202 
203 	if (std::getline(catalogFile, currentItem, '\t').good()) {
204 		// Get the signature
205 		fSignature << currentItem.c_str() ;
206 	} else {
207 		log_team(LOG_DEBUG, "Unable to get signature from %s", path);
208 		return B_ERROR;
209 	}
210 
211 	if (std::getline(catalogFile, currentItem).good()) {
212 		// Get the fingerprint
213 		std::istringstream ss(currentItem);
214 		uint32 foundFingerprint;
215 		ss >> foundFingerprint;
216 		if (ss.fail()) {
217 			log_team(LOG_DEBUG, "Unable to get fingerprint (%s) from %s",
218 					currentItem.c_str(), path);
219 			return B_ERROR;
220 		}
221 
222 		if (fFingerprint!=0 && fFingerprint != foundFingerprint) {
223 			return B_MISMATCHED_VALUES;
224 		}
225 	} else {
226 		log_team(LOG_DEBUG, "Unable to get fingerprint from %s", path);
227 		return B_ERROR;
228 	}
229 
230 	// We managed to open the file, so we remember it's the one we are using
231 	fPath = path;
232 	log_team(LOG_DEBUG, "found plaintext catalog at %s", path);
233 
234 	std::string originalString;
235 	std::string context;
236 	std::string comment;
237 	std::string translated;
238 
239 	while (std::getline(catalogFile, originalString,'\t').good()) {
240 		// Each line is : "original string \t key \t translated string"
241 		// However, the original string is also tab-separated because it
242 		// also holds the context and comment
243 
244 		if (!std::getline(catalogFile,context,'\t').good()) {
245 			log_team(LOG_DEBUG, "Unable to get context for string %s from %s",
246 				originalString.c_str(), path);
247 			return B_ERROR;
248 		}
249 
250 		if (!std::getline(catalogFile,comment,'\t').good()) {
251 			log_team(LOG_DEBUG, "Unable to get comment for string %s from %s",
252 				originalString.c_str(), path);
253 			return B_ERROR;
254 		}
255 
256 		if (!std::getline(catalogFile,translated).good()) {
257 			log_team(LOG_DEBUG,
258 				"Unable to get translated text for string %s from %s",
259 				originalString.c_str(), path);
260 			return B_ERROR;
261 		}
262 
263 		// We could do that :
264 		// SetString(key, translated.c_str());
265 		// but we want to keep the strings in the new catkey. Hash collisions
266 		// happen, you know. (and CatKey::== will fail)
267 		SetString(originalString.c_str(), translated.c_str(), context.c_str(),
268 			comment.c_str());
269 		log_team(LOG_DEBUG, "Added string %s from file %s",
270 			originalString.c_str(), path);
271 	}
272 
273 	catalogFile.close();
274 
275 	// some information living in member variables needs to be copied
276 	// to attributes. Although these attributes should have been written
277 	// when creating the catalog, we make sure that they exist there:
278 	UpdateAttributes(path);
279 	return B_OK;
280 }
281 
282 
283 status_t
284 PlainTextCatalog::WriteToFile(const char *path)
285 {
286 	BString textContent;
287 	BFile catalogFile;
288 
289 	if (path)
290 		fPath = path;
291 	status_t res = catalogFile.SetTo(fPath.String(),
292 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
293 	if (res != B_OK)
294 		return res;
295 
296 	UpdateFingerprint();
297 		// make sure we have the correct fingerprint before we flatten it
298 
299 
300 	textContent << kCatArchiveVersion << "\t" << fLanguageName.String() << "\t"
301 		<< fSignature.String() << "\t" << fFingerprint << "\n";
302 
303 	res = catalogFile.Write(textContent.String(),textContent.Length());
304 	if (res != textContent.Length())
305 		return res;
306 
307 	CatMap::Iterator iter = fCatMap.GetIterator();
308 	CatMap::Entry entry;
309 	BString original;
310 	BString translated;
311 
312 	while (iter.HasNext()) {
313 		entry = iter.Next();
314 		original = entry.key.fString;
315 		translated = entry.value;
316 		escapeQuotedChars(original);
317 		escapeQuotedChars(translated);
318 		textContent.Truncate(0);
319 		textContent << original.String() << "\t"
320 			<< entry.key.fContext.String() << "\t"
321 			<< entry.key.fComment.String() << "\t"
322 			<< translated.String() << "\n";
323 		res = catalogFile.Write(textContent.String(),textContent.Length());
324 		if (res != textContent.Length())
325 			return res;
326 	}
327 
328 	// set mimetype-, language- and signature-attributes:
329 	UpdateAttributes(catalogFile);
330 
331 	return B_OK;
332 }
333 
334 
335 /*
336  * writes mimetype, language-name and signature of catalog into the
337  * catalog-file.
338  */
339 void
340 PlainTextCatalog::UpdateAttributes(BFile& catalogFile)
341 {
342 	static const int bufSize = 256;
343 	char buf[bufSize];
344 	uint32 temp;
345 	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, bufSize)
346 			<= 0
347 		|| strcmp(kCatMimeType, buf) != 0) {
348 		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
349 			kCatMimeType, strlen(kCatMimeType)+1);
350 	}
351 	if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
352 		&buf, bufSize) <= 0
353 		|| fLanguageName != buf) {
354 		catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
355 			fLanguageName.String(), fLanguageName.Length()+1);
356 	}
357 	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
358 		&buf, bufSize) <= 0
359 		|| fSignature != buf) {
360 		catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
361 			fSignature.String(), fSignature.Length()+1);
362 	}
363 	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
364 		0, &temp, sizeof(uint32)) <= 0) {
365 		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
366 			0, &fFingerprint, sizeof(uint32));
367 	}
368 }
369 
370 
371 void
372 PlainTextCatalog::UpdateAttributes(const char* path)
373 {
374 	BEntry entry(path);
375 	BFile node(&entry, B_READ_WRITE);
376 	UpdateAttributes(node);
377 }
378 
379 
380 BCatalogAddOn *
381 PlainTextCatalog::Instantiate(const char *signature, const char *language,
382 	uint32 fingerprint)
383 {
384 	PlainTextCatalog *catalog
385 		= new(std::nothrow) PlainTextCatalog(signature, language, fingerprint);
386 	if (catalog && catalog->InitCheck() != B_OK) {
387 		delete catalog;
388 		return NULL;
389 	}
390 	return catalog;
391 }
392 
393 
394 extern "C" BCatalogAddOn *
395 instantiate_catalog(const char *signature, const char *language,
396 	uint32 fingerprint)
397 {
398 	PlainTextCatalog *catalog
399 		= new(std::nothrow) PlainTextCatalog(signature, language, fingerprint);
400 	if (catalog && catalog->InitCheck() != B_OK) {
401 		delete catalog;
402 		return NULL;
403 	}
404 	return catalog;
405 }
406 
407 
408 extern "C"
409 BCatalogAddOn *create_catalog(const char *signature,
410 	const char *language)
411 {
412 	PlainTextCatalog *catalog =
413 		new(std::nothrow) PlainTextCatalog("emptycat", signature, language);
414 	return catalog;
415 }
416 
417 
418 uint8 gCatalogAddOnPriority = 99;
419 	// give low priority to this add on, we don't want it to be actually used
420