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 <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
33using BPrivate::HashMapCatalog;
34using BPrivate::PlainTextCatalog;
35using std::auto_ptr;
36using std::min;
37using std::max;
38using 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
48extern "C" uint32 adler32(uint32 adler, const uint8 *buf, uint32 len);
49	// definition lives in adler32.c
50
51static const char *kCatFolder = "catalogs";
52static const char *kCatExtension = ".catkeys";
53
54const char *PlainTextCatalog::kCatMimeType
55	= "locale/x-vnd.Be.locale-catalog.plaintext";
56
57static int16 kCatArchiveVersion = 1;
58	// version of the catalog archive structure, bump this if you change it!
59
60
61void
62escapeQuotedChars(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 */
77PlainTextCatalog::PlainTextCatalog(const char *signature, const char *language,
78	uint32 fingerprint)
79	:
80	HashMapCatalog(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}
127
128
129/*
130 * constructs an empty PlainTextCatalog with given sig and language.
131 * This is used for editing/testing purposes.
132 * InitCheck() will always be B_OK.
133 */
134PlainTextCatalog::PlainTextCatalog(const char *path, const char *signature,
135	const char *language)
136	:
137	HashMapCatalog(signature, language, 0),
138	fPath(path)
139{
140	fInitCheck = B_OK;
141}
142
143
144PlainTextCatalog::~PlainTextCatalog()
145{
146}
147
148
149status_t
150PlainTextCatalog::ReadFromFile(const char *path)
151{
152	std::fstream catalogFile;
153	std::string currentItem;
154
155	if (!path)
156		path = fPath.String();
157
158	catalogFile.open(path, std::fstream::in);
159	if (!catalogFile.is_open())
160		return B_ENTRY_NOT_FOUND;
161
162	// Now read all the data from the file
163
164	// The first line holds some info about the catalog :
165	// ArchiveVersion \t LanguageName \t Signature \t FingerPrint
166	if (std::getline(catalogFile, currentItem, '\t').good()) {
167		// Get the archive version
168		int arcver= -1;
169		std::istringstream ss(currentItem);
170		ss >> arcver;
171		if (ss.fail()) {
172			// can't convert to int
173			return B_ERROR;
174		}
175
176		if (arcver != kCatArchiveVersion) {
177			// wrong version
178			return B_ERROR;
179		}
180	} else
181		return B_ERROR;
182
183	if (std::getline(catalogFile, currentItem, '\t').good()) {
184		// Get the language
185		fLanguageName = currentItem.c_str() ;
186	} else
187		return B_ERROR;
188
189	if (std::getline(catalogFile, currentItem, '\t').good()) {
190		// Get the signature
191		fSignature = currentItem.c_str() ;
192	} else
193		return B_ERROR;
194
195	if (std::getline(catalogFile, currentItem).good()) {
196		// Get the fingerprint
197		std::istringstream ss(currentItem);
198		uint32 foundFingerprint;
199		ss >> foundFingerprint;
200		if (ss.fail())
201			return B_ERROR;
202
203		if (fFingerprint == 0)
204			fFingerprint = foundFingerprint;
205
206		if (fFingerprint != foundFingerprint) {
207			return B_MISMATCHED_VALUES;
208		}
209	} else
210		return B_ERROR;
211
212	// We managed to open the file, so we remember it's the one we are using
213	fPath = path;
214
215	std::string originalString;
216	std::string context;
217	std::string comment;
218	std::string translated;
219
220	while (std::getline(catalogFile, originalString,'\t').good()) {
221		// Each line is : "original string \t context \t comment \t translation"
222
223		if (!std::getline(catalogFile, context,'\t').good())
224			return B_ERROR;
225
226		if (!std::getline(catalogFile, comment,'\t').good())
227			return B_ERROR;
228
229		if (!std::getline(catalogFile, translated).good())
230			return B_ERROR;
231
232		// We could do that :
233		// SetString(key, translated.c_str());
234		// but we want to keep the strings in the new catkey. Hash collisions
235		// happen, you know. (and CatKey::== will fail)
236		SetString(originalString.c_str(), translated.c_str(), context.c_str(),
237			comment.c_str());
238	}
239
240	catalogFile.close();
241
242	uint32 checkFP = ComputeFingerprint();
243	if (fFingerprint != checkFP)
244		return B_BAD_DATA;
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	static const int bufSize = 256;
318	char buf[bufSize];
319	uint32 temp;
320	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, bufSize)
321			<= 0
322		|| strcmp(kCatMimeType, buf) != 0) {
323		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
324			kCatMimeType, strlen(kCatMimeType)+1);
325	}
326	if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
327		&buf, bufSize) <= 0
328		|| fLanguageName != buf) {
329		catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
330			fLanguageName.String(), fLanguageName.Length()+1);
331	}
332	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
333		&buf, bufSize) <= 0
334		|| fSignature != buf) {
335		catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
336			fSignature.String(), fSignature.Length()+1);
337	}
338	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
339		0, &temp, sizeof(uint32)) <= 0) {
340		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
341			0, &fFingerprint, sizeof(uint32));
342	}
343}
344
345
346void
347PlainTextCatalog::UpdateAttributes(const char* path)
348{
349	BEntry entry(path);
350	BFile node(&entry, B_READ_WRITE);
351	UpdateAttributes(node);
352}
353
354
355BCatalogData *
356PlainTextCatalog::Instantiate(const char *signature, const char *language,
357	uint32 fingerprint)
358{
359	PlainTextCatalog *catalog
360		= new(std::nothrow) PlainTextCatalog(signature, language, fingerprint);
361	if (catalog && catalog->InitCheck() != B_OK) {
362		delete catalog;
363		return NULL;
364	}
365	return catalog;
366}
367
368
369extern "C" BCatalogData *
370instantiate_catalog(const char *signature, const char *language,
371	uint32 fingerprint)
372{
373	PlainTextCatalog *catalog
374		= new(std::nothrow) PlainTextCatalog(signature, language, fingerprint);
375	if (catalog && catalog->InitCheck() != B_OK) {
376		delete catalog;
377		return NULL;
378	}
379	return catalog;
380}
381
382
383extern "C" BCatalogData *
384create_catalog(const char *signature, const char *language)
385{
386	PlainTextCatalog *catalog
387		= new(std::nothrow) PlainTextCatalog("emptycat", signature, language);
388	return catalog;
389}
390
391
392extern "C" status_t
393get_available_languages(BMessage* availableLanguages,
394	const char* sigPattern = NULL, const char* langPattern = NULL,
395	int32 fingerprint = 0)
396{
397	// TODO
398	return B_ERROR;
399}
400
401
402uint8 gCatalogAddOnPriority = 99;
403	// give low priority to this add on, we don't want it to be actually used
404