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