1/*
2 * Copyright 2003-2009, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Oliver Tappe, zooey@hirschkaefer.de
7 *		Adrien Destugues, pulkomandy@gmail.com
8 */
9
10
11#include <memory>
12#include <new>
13#include <syslog.h>
14
15#include <AppFileInfo.h>
16#include <Application.h>
17#include <DataIO.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
28#include <DefaultCatalog.h>
29#include <LocaleRoster.h>
30
31#include <cstdio>
32
33
34using BPrivate::DefaultCatalog;
35using std::auto_ptr;
36
37
38/*!	This file implements the default catalog-type for the opentracker locale
39	kit. Alternatively, this could be used as a full add-on, but currently this
40	is provided as part of liblocale.so.
41*/
42
43
44namespace BPrivate {
45
46
47// several attributes/resource-IDs used within the Locale Kit:
48
49const char *kCatLangAttr = "BEOS:LOCALE_LANGUAGE";
50	// name of catalog language, lives in every catalog file
51const char *kCatSigAttr = "BEOS:LOCALE_SIGNATURE";
52	// catalog signature, lives in every catalog file
53const char *kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT";
54	// catalog fingerprint, may live in catalog file
55
56const char *DefaultCatalog::kCatMimeType
57	= "locale/x-vnd.Be.locale-catalog.default";
58
59static int16 kCatArchiveVersion = 1;
60	// version of the catalog archive structure, bump this if you change it!
61
62
63/*!	Constructs a DefaultCatalog with given signature and language and reads
64	the catalog from disk.
65	InitCheck() will be B_OK if catalog could be loaded successfully, it will
66	give an appropriate error-code otherwise.
67*/
68DefaultCatalog::DefaultCatalog(const entry_ref &catalogOwner,
69	const char *language, uint32 fingerprint)
70	:
71	HashMapCatalog("", language, fingerprint)
72{
73	fInitCheck = B_NOT_SUPPORTED;
74	fprintf(stderr,
75		"trying to load default-catalog(sig=%s, lang=%s) results in %s",
76		"", language, strerror(fInitCheck));
77}
78
79
80/*!	Constructs a DefaultCatalog and reads it from the resources of the
81	given entry-ref (which usually is an app- or add-on-file).
82	InitCheck() will be B_OK if catalog could be loaded successfully, it will
83	give an appropriate error-code otherwise.
84*/
85DefaultCatalog::DefaultCatalog(entry_ref *appOrAddOnRef)
86	:
87	HashMapCatalog("", "", 0)
88{
89	fInitCheck = ReadFromResource(*appOrAddOnRef);
90	// fprintf(stderr,
91	//	"trying to load embedded catalog from resources results in %s",
92	//	strerror(fInitCheck));
93}
94
95
96/*!	Constructs an empty DefaultCatalog with given sig and language.
97	This is used for editing/testing purposes.
98	InitCheck() will always be B_OK.
99*/
100DefaultCatalog::DefaultCatalog(const char *path, const char *signature,
101	const char *language)
102	:
103	HashMapCatalog(signature, language, 0),
104	fPath(path)
105{
106	fInitCheck = B_OK;
107}
108
109
110DefaultCatalog::~DefaultCatalog()
111{
112}
113
114
115void
116DefaultCatalog::SetSignature(const entry_ref &catalogOwner)
117{
118	// Not allowed for the build-tool version.
119	return;
120}
121
122
123status_t
124DefaultCatalog::SetRawString(const CatKey& key, const char *translated)
125{
126	return fCatMap.Put(key, translated);
127}
128
129
130status_t
131DefaultCatalog::ReadFromFile(const char *path)
132{
133	if (!path)
134		path = fPath.String();
135
136	BFile catalogFile;
137	status_t res = catalogFile.SetTo(path, B_READ_ONLY);
138	if (res != B_OK) {
139		fprintf(stderr, "no catalog at %s\n", path);
140		return B_ENTRY_NOT_FOUND;
141	}
142
143	fPath = path;
144	fprintf(stderr, "found catalog at %s\n", path);
145
146	off_t sz = 0;
147	res = catalogFile.GetSize(&sz);
148	if (res != B_OK) {
149		fprintf(stderr, "couldn't get size for catalog-file %s\n", path);
150		return res;
151	}
152
153	auto_ptr<char> buf(new(std::nothrow) char [sz]);
154	if (buf.get() == NULL) {
155		fprintf(stderr, "couldn't allocate array of %Ld chars\n", sz);
156		return B_NO_MEMORY;
157	}
158	res = catalogFile.Read(buf.get(), sz);
159	if (res < B_OK) {
160		fprintf(stderr, "couldn't read from catalog-file %s\n", path);
161		return res;
162	}
163	if (res < sz) {
164		fprintf(stderr,
165			"only got %u instead of %Lu bytes from catalog-file %s\n", res, sz,
166			path);
167		return res;
168	}
169	BMemoryIO memIO(buf.get(), sz);
170	res = Unflatten(&memIO);
171
172	if (res == B_OK) {
173		// some information living in member variables needs to be copied
174		// to attributes. Although these attributes should have been written
175		// when creating the catalog, we make sure that they exist there:
176		UpdateAttributes(catalogFile);
177	}
178
179	return res;
180}
181
182
183/*!	This method is not currently being used, but it may be useful in the
184	future...
185*/
186status_t
187DefaultCatalog::ReadFromAttribute(const entry_ref &appOrAddOnRef)
188{
189	return B_NOT_SUPPORTED;
190}
191
192
193status_t
194DefaultCatalog::ReadFromResource(const entry_ref &appOrAddOnRef)
195{
196	return B_NOT_SUPPORTED;
197}
198
199
200status_t
201DefaultCatalog::WriteToFile(const char *path)
202{
203	BFile catalogFile;
204	if (path)
205		fPath = path;
206	status_t status = catalogFile.SetTo(fPath.String(),
207		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
208	if (status != B_OK)
209		return status;
210
211	BMallocIO mallocIO;
212	mallocIO.SetBlockSize(max_c(fCatMap.Size() * 20, 256));
213		// set a largish block-size in order to avoid reallocs
214	status = Flatten(&mallocIO);
215	if (status != B_OK)
216		return status;
217
218	ssize_t bytesWritten
219		= catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength());
220	if (bytesWritten < 0)
221		return bytesWritten;
222	if (bytesWritten != (ssize_t)mallocIO.BufferLength())
223		return B_IO_ERROR;
224
225	// set mimetype-, language- and signature-attributes:
226	UpdateAttributes(catalogFile);
227
228	return B_OK;
229}
230
231
232/*!	This method is not currently being used, but it may be useful in the
233	future...
234*/
235status_t
236DefaultCatalog::WriteToAttribute(const entry_ref &appOrAddOnRef)
237{
238	return B_NOT_SUPPORTED;
239}
240
241
242status_t
243DefaultCatalog::WriteToResource(const entry_ref &appOrAddOnRef)
244{
245	return B_NOT_SUPPORTED;
246}
247
248
249/*!	Writes mimetype, language-name and signature of catalog into the
250	catalog-file.
251*/
252void
253DefaultCatalog::UpdateAttributes(BFile& catalogFile)
254{
255	static const int bufSize = 256;
256	char buf[bufSize];
257	uint32 temp;
258	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf,
259			bufSize) <= 0
260		|| strcmp(kCatMimeType, buf) != 0) {
261		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
262			kCatMimeType, strlen(kCatMimeType)+1);
263	}
264	if (catalogFile.ReadAttr(kCatLangAttr, B_STRING_TYPE, 0,
265			&buf, bufSize) <= 0
266		|| fLanguageName != buf) {
267		catalogFile.WriteAttr(kCatLangAttr, B_STRING_TYPE, 0,
268			fLanguageName.String(), fLanguageName.Length()+1);
269	}
270	if (catalogFile.ReadAttr(kCatSigAttr, B_STRING_TYPE, 0,
271			&buf, bufSize) <= 0
272		|| fSignature != buf) {
273		catalogFile.WriteAttr(kCatSigAttr, B_STRING_TYPE, 0,
274			fSignature.String(), fSignature.Length()+1);
275	}
276	if (catalogFile.ReadAttr(kCatFingerprintAttr, B_UINT32_TYPE,
277		0, &temp, sizeof(uint32)) <= 0) {
278		catalogFile.WriteAttr(kCatFingerprintAttr, B_UINT32_TYPE,
279			0, &fFingerprint, sizeof(uint32));
280	}
281}
282
283
284status_t
285DefaultCatalog::Flatten(BDataIO *dataIO)
286{
287	UpdateFingerprint();
288		// make sure we have the correct fingerprint before we flatten it
289
290	status_t res;
291	BMessage archive;
292	uint32 count = fCatMap.Size();
293	res = archive.AddString("class", "DefaultCatalog");
294	if (res == B_OK)
295		res = archive.AddInt32("c:sz", count);
296	if (res == B_OK)
297		res = archive.AddInt16("c:ver", kCatArchiveVersion);
298	if (res == B_OK)
299		res = archive.AddString("c:lang", fLanguageName.String());
300	if (res == B_OK)
301		res = archive.AddString("c:sig", fSignature.String());
302	if (res == B_OK)
303		res = archive.AddInt32("c:fpr", fFingerprint);
304	if (res == B_OK)
305		res = archive.Flatten(dataIO);
306
307	CatMap::Iterator iter = fCatMap.GetIterator();
308	CatMap::Entry entry;
309	while (res == B_OK && iter.HasNext()) {
310		entry = iter.Next();
311		archive.MakeEmpty();
312		res = archive.AddString("c:ostr", entry.key.fString.String());
313		if (res == B_OK)
314			res = archive.AddString("c:ctxt", entry.key.fContext.String());
315		if (res == B_OK)
316			res = archive.AddString("c:comt", entry.key.fComment.String());
317		if (res == B_OK)
318			res = archive.AddInt32("c:hash", entry.key.fHashVal);
319		if (res == B_OK)
320			res = archive.AddString("c:tstr", entry.value.String());
321		if (res == B_OK)
322			res = archive.Flatten(dataIO);
323	}
324	return res;
325}
326
327
328status_t
329DefaultCatalog::Unflatten(BDataIO *dataIO)
330{
331	fCatMap.Clear();
332	int32 count = 0;
333	int16 version;
334	BMessage archiveMsg;
335	status_t res = archiveMsg.Unflatten(dataIO);
336
337	if (res == B_OK) {
338		res = archiveMsg.FindInt16("c:ver", &version)
339			|| archiveMsg.FindInt32("c:sz", &count);
340	}
341	if (res == B_OK) {
342		fLanguageName = archiveMsg.FindString("c:lang");
343		fSignature = archiveMsg.FindString("c:sig");
344		uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr");
345
346		// if a specific fingerprint has been requested and the catalog does in
347		// fact have a fingerprint, both are compared. If they mismatch, we do
348		// not accept this catalog:
349		if (foundFingerprint != 0 && fFingerprint != 0
350			&& foundFingerprint != fFingerprint) {
351			fprintf(stderr, "default-catalog(sig=%s, lang=%s) "
352				"has mismatching fingerprint (%d instead of the requested %d)"
353				", so this catalog is skipped.\n",
354				fSignature.String(), fLanguageName.String(), foundFingerprint,
355				fFingerprint);
356			res = B_MISMATCHED_VALUES;
357		} else
358			fFingerprint = foundFingerprint;
359	}
360
361	if (res == B_OK && count > 0) {
362		CatKey key;
363		const char *keyStr;
364		const char *keyCtx;
365		const char *keyCmt;
366		const char *translated;
367
368		// fCatMap.resize(count);
369			// There is no resize method in Haiku Hash Map to prealloc space
370		for (int i=0; res == B_OK && i < count; ++i) {
371			res = archiveMsg.Unflatten(dataIO);
372			if (res == B_OK)
373				res = archiveMsg.FindString("c:ostr", &keyStr);
374			if (res == B_OK)
375				res = archiveMsg.FindString("c:ctxt", &keyCtx);
376			if (res == B_OK)
377				res = archiveMsg.FindString("c:comt", &keyCmt);
378			if (res == B_OK)
379				res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal);
380			if (res == B_OK)
381				res = archiveMsg.FindString("c:tstr", &translated);
382			if (res == B_OK) {
383				key.fString = keyStr;
384				key.fContext = keyCtx;
385				key.fComment = keyCmt;
386				fCatMap.Put(key, translated);
387			}
388		}
389		uint32 checkFP = ComputeFingerprint();
390		if (fFingerprint != checkFP) {
391			fprintf(stderr, "default-catalog(sig=%s, lang=%s) "
392				"has wrong fingerprint after load (%d instead of %d). "
393				"The catalog data may be corrupted, so this catalog is "
394				"skipped.\n",
395				fSignature.String(), fLanguageName.String(), checkFP,
396				fFingerprint);
397			return B_BAD_DATA;
398		}
399	}
400	return res;
401}
402
403
404BCatalogData *
405DefaultCatalog::Instantiate(const entry_ref &catalogOwner, const char *language,
406	uint32 fingerprint)
407{
408	DefaultCatalog *catalog
409		= new(std::nothrow) DefaultCatalog(catalogOwner, language, fingerprint);
410	if (catalog && catalog->InitCheck() != B_OK) {
411		delete catalog;
412		return NULL;
413	}
414	return catalog;
415}
416
417
418BCatalogData *
419DefaultCatalog::Create(const char *signature, const char *language)
420{
421	DefaultCatalog *catalog
422		= new(std::nothrow) DefaultCatalog("", signature, language);
423	if (catalog && catalog->InitCheck() != B_OK) {
424		delete catalog;
425		return NULL;
426	}
427	return catalog;
428}
429
430
431const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1;
432	// give highest priority to our embedded catalog-add-on
433
434
435} // namespace BPrivate
436