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 <syslog.h>
17
18#include <Application.h>
19#include <Directory.h>
20#include <File.h>
21#include <FindDirectory.h>
22#include <fs_attr.h>
23#include <Message.h>
24#include <Mime.h>
25#include <Path.h>
26#include <Resources.h>
27#include <Roster.h>
28#include <String.h>
29
30#include <Catalog.h>
31
32
33using BPrivate::PlainTextCatalog;
34using std::min;
35using std::max;
36using std::pair;
37
38
39/*
40 *	This file implements the plain text catalog-type for the Haiku
41 *	locale kit. It is not meant to be used in applications like other add ons,
42 *	but only as a way to get an easy to translate file for developers.
43 */
44
45
46namespace BPrivate {
47
48
49const char *PlainTextCatalog::kCatMimeType
50	= "locale/x-vnd.Be.locale-catalog.plaintext";
51
52static int16 kCatArchiveVersion = 1;
53	// version of the catalog archive structure, bump this if you change it!
54
55
56// Note: \xNN is not replaced back, so you get the unescaped value in the catkey
57// file. This is fine for regular unicode chars (B_UTF8_ELLIPSIS) but may be
58// dangerous if you use \x10 as a newline...
59void
60escapeQuotedChars(BString& stringToEscape)
61{
62	stringToEscape.ReplaceAll("\\", "\\\\");
63	stringToEscape.ReplaceAll("\n", "\\n");
64	stringToEscape.ReplaceAll("\t", "\\t");
65	stringToEscape.ReplaceAll("\"", "\\\"");
66}
67
68
69/*
70 * constructs a PlainTextCatalog with given signature and language and reads
71 * the catalog from disk.
72 * InitCheck() will be B_OK if catalog could be loaded successfully, it will
73 * give an appropriate error-code otherwise.
74 */
75PlainTextCatalog::PlainTextCatalog(const entry_ref &signature,
76	const char *language, uint32 fingerprint)
77	:
78	HashMapCatalog("", language, fingerprint)
79{
80	// Look for the catalog in the directory we are going to use
81	// This constructor is not used so lets disable that...
82
83	fInitCheck = B_NOT_SUPPORTED;
84	fprintf(stderr,
85		"trying to load plaintext-catalog(lang=%s) results in %s\n",
86		language, strerror(fInitCheck));
87}
88
89
90/*
91 * constructs an empty PlainTextCatalog with given sig and language.
92 * This is used for editing/testing purposes.
93 * InitCheck() will always be B_OK.
94 */
95PlainTextCatalog::PlainTextCatalog(const char *path, const char *signature,
96	const char *language)
97	:
98	HashMapCatalog(signature, language, 0),
99	fPath(path)
100{
101	fInitCheck = B_OK;
102}
103
104
105PlainTextCatalog::~PlainTextCatalog()
106{
107}
108
109
110status_t
111PlainTextCatalog::ReadFromFile(const char *path)
112{
113	std::fstream catalogFile;
114	std::string  currentItem;
115
116	if (!path)
117		path = fPath.String();
118
119	catalogFile.open(path, std::fstream::in);
120	if (!catalogFile.is_open()) {
121		fprintf(stderr, "couldn't open catalog at %s\n", path);
122		return B_ENTRY_NOT_FOUND;
123	}
124
125	// Now read all the data from the file
126
127	// The first line holds some info about the catalog :
128	// ArchiveVersion \t LanguageName \t AppSignature \t FingerPrint
129	if (std::getline(catalogFile, currentItem, '\t').good()) {
130		// Get the archive version
131		int arcver= -1;
132		std::istringstream ss(currentItem);
133		ss >> arcver;
134		if (ss.fail()) {
135	 		// can't convert to int
136			fprintf(stderr,
137				"Unable to extract archive version ( string: %s ) from %s\n",
138				currentItem.c_str(), path);
139			return B_ERROR;
140		}
141
142		if (arcver != kCatArchiveVersion) {
143			// wrong version
144			fprintf(stderr,
145				"Wrong archive version ! Got %d instead of %d from %s\n", arcver,
146				kCatArchiveVersion, path);
147			return B_ERROR;
148		}
149	} else {
150		fprintf(stderr, "Unable to read from catalog %s\n", path);
151		return B_ERROR;
152	}
153
154	if (std::getline(catalogFile, currentItem, '\t').good()) {
155		// Get the language
156		fLanguageName = currentItem.c_str() ;
157	} else {
158		fprintf(stderr, "Unable to get language from %s\n", path);
159		return B_ERROR;
160	}
161
162	if (std::getline(catalogFile, currentItem, '\t').good()) {
163		// Get the signature
164		fSignature = currentItem.c_str() ;
165	} else {
166		fprintf(stderr, "Unable to get signature from %s\n", path);
167		return B_ERROR;
168	}
169
170	if (std::getline(catalogFile, currentItem).good()) {
171		// Get the fingerprint
172		std::istringstream ss(currentItem);
173		uint32 foundFingerprint;
174		ss >> foundFingerprint;
175		if (ss.fail()) {
176			fprintf(stderr, "Unable to get fingerprint (%s) from %s\n",
177					currentItem.c_str(), path);
178			return B_ERROR;
179		}
180
181		if (fFingerprint == 0)
182			fFingerprint = foundFingerprint;
183
184		if (fFingerprint != foundFingerprint) {
185			return B_MISMATCHED_VALUES;
186		}
187	} else {
188		fprintf(stderr, "Unable to get fingerprint from %s\n", path);
189		return B_ERROR;
190	}
191
192	// We managed to open the file, so we remember it's the one we are using
193	fPath = path;
194	// fprintf(stderr, "LocaleKit Plaintext: found catalog at %s\n", path);
195
196	std::string originalString;
197	std::string context;
198	std::string comment;
199	std::string translated;
200
201	while (std::getline(catalogFile, originalString,'\t').good()) {
202		// Each line is : "original string \t context \t comment \t translation"
203
204		if (!std::getline(catalogFile, context,'\t').good()) {
205			fprintf(stderr, "Unable to get context for string %s from %s\n",
206				originalString.c_str(), path);
207			return B_ERROR;
208		}
209
210		if (!std::getline(catalogFile, comment,'\t').good()) {
211			fprintf(stderr, "Unable to get comment for string %s from %s\n",
212				originalString.c_str(), path);
213			return B_ERROR;
214		}
215
216		if (!std::getline(catalogFile, translated).good()) {
217			fprintf(stderr,
218				"Unable to get translated text for string %s from %s\n",
219				originalString.c_str(), path);
220			return B_ERROR;
221		}
222
223		// We could do that :
224		// SetString(key, translated.c_str());
225		// but we want to keep the strings in the new catkey. Hash collisions
226		// happen, you know. (and CatKey::== will fail)
227		SetString(originalString.c_str(), translated.c_str(), context.c_str(),
228			comment.c_str());
229	}
230
231	catalogFile.close();
232
233#if 0
234	uint32 checkFP = ComputeFingerprint();
235	if (fFingerprint != checkFP) {
236		fprintf(stderr, "plaintext-catalog(sig=%s, lang=%s) "
237			"has wrong fingerprint after load (%u instead of %u). "
238			"The catalog data may be corrupted, so this catalog is "
239			"skipped.\n",
240			fSignature.String(), fLanguageName.String(), checkFP,
241			fFingerprint);
242		return B_BAD_DATA;
243	}
244#endif
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
254status_t
255PlainTextCatalog::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 */
314void
315PlainTextCatalog::UpdateAttributes(BFile& catalogFile)
316{
317	// useless on the build-tool-side
318}
319
320
321void
322PlainTextCatalog::UpdateAttributes(const char* path)
323{
324	BEntry entry(path);
325	BFile node(&entry, B_READ_WRITE);
326	UpdateAttributes(node);
327}
328
329
330BCatalogData *
331PlainTextCatalog::Instantiate(const entry_ref &owner, const char *language,
332	uint32 fingerprint)
333{
334	PlainTextCatalog *catalog
335		= new(std::nothrow) PlainTextCatalog(owner, language, fingerprint);
336	if (catalog && catalog->InitCheck() != B_OK) {
337		delete catalog;
338		return NULL;
339	}
340	return catalog;
341}
342
343
344} // namespace BPrivate
345
346
347extern "C" BCatalogData *
348instantiate_catalog(const entry_ref &owner, const char *language,
349	uint32 fingerprint)
350{
351	return PlainTextCatalog::Instantiate(owner, language, fingerprint);
352}
353
354
355extern "C" BCatalogData *
356create_catalog(const char *signature, const char *language)
357{
358	PlainTextCatalog *catalog
359		= new(std::nothrow) PlainTextCatalog("emptycat", signature, language);
360	return catalog;
361}
362
363
364uint8 gCatalogAddOnPriority = 99;
365	// give low priority to this add on, we don't want it to be actually used
366