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