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 <algorithm>
12#include <new>
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 <MutableLocaleRoster.h>
30
31
32#include <cstdio>
33
34
35using std::min;
36using std::max;
37using std::pair;
38
39
40/*!	This file implements the default catalog-type for the opentracker locale
41	kit. Alternatively, this could be used as a full add-on, but currently this
42	is provided as part of liblocale.so.
43*/
44
45
46static const char *kCatFolder = "catalogs";
47static const char *kCatExtension = ".catalog";
48
49
50namespace BPrivate {
51
52
53const char *DefaultCatalog::kCatMimeType
54	= "locale/x-vnd.Be.locale-catalog.default";
55
56static int16 kCatArchiveVersion = 1;
57	// version of the catalog archive structure, bump this if you change it!
58
59const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1;
60	// give highest priority to our embedded catalog-add-on
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	// We created the catalog with an invalid signature, but we fix that now.
74	SetSignature(catalogOwner);
75	status_t status;
76
77	// search for catalog living in sub-folder of app's folder:
78	node_ref nref;
79	nref.device = catalogOwner.device;
80	nref.node = catalogOwner.directory;
81	BDirectory appDir(&nref);
82	BString catalogName("locale/");
83	catalogName << kCatFolder
84		<< "/" << fSignature
85		<< "/" << fLanguageName
86		<< kCatExtension;
87	BPath catalogPath(&appDir, catalogName.String());
88	status = ReadFromFile(catalogPath.Path());
89
90	// search for catalogs in the standard ../data/locale/ directories
91	// (packaged/non-packaged and system/home)
92	if (status != B_OK)
93		status = ReadFromStandardLocations();
94
95	if (status != B_OK) {
96		// give lowest priority to catalog embedded as resource in application
97		// executable, so they can be overridden easily.
98		status = ReadFromResource(catalogOwner);
99	}
100
101	fInitCheck = status;
102}
103
104
105/*!	Constructs a DefaultCatalog and reads it from the resources of the
106	given entry-ref (which usually is an app- or add-on-file).
107	InitCheck() will be B_OK if catalog could be loaded successfully, it will
108	give an appropriate error-code otherwise.
109*/
110DefaultCatalog::DefaultCatalog(entry_ref *appOrAddOnRef)
111	:
112	HashMapCatalog("", "", 0)
113{
114	fInitCheck = ReadFromResource(*appOrAddOnRef);
115}
116
117
118/*!	Constructs an empty DefaultCatalog with given sig and language.
119	This is used for editing/testing purposes.
120	InitCheck() will always be B_OK.
121*/
122DefaultCatalog::DefaultCatalog(const char *path, const char *signature,
123	const char *language)
124	:
125	HashMapCatalog(signature, language, 0),
126	fPath(path)
127{
128	fInitCheck = B_OK;
129}
130
131
132DefaultCatalog::~DefaultCatalog()
133{
134}
135
136
137void
138DefaultCatalog::SetSignature(const entry_ref &catalogOwner)
139{
140	// figure out mimetype from image
141	BFile objectFile(&catalogOwner, B_READ_ONLY);
142	BAppFileInfo objectInfo(&objectFile);
143	char objectSignature[B_MIME_TYPE_LENGTH];
144	if (objectInfo.GetSignature(objectSignature) != B_OK) {
145		fSignature = "";
146		return;
147	}
148
149	// drop supertype from mimetype (should be "application/"):
150	char* stripSignature = objectSignature;
151	while (*stripSignature != '/' && *stripSignature != '\0')
152		stripSignature ++;
153
154	if (*stripSignature == '\0')
155		stripSignature = objectSignature;
156	else
157		stripSignature ++;
158
159	fSignature = stripSignature;
160}
161
162
163status_t
164DefaultCatalog::SetRawString(const CatKey& key, const char *translated)
165{
166	return fCatMap.Put(key, translated);
167}
168
169
170status_t
171DefaultCatalog::ReadFromStandardLocations()
172{
173	// search in data folders
174
175	directory_which which[] = {
176		B_USER_NONPACKAGED_DATA_DIRECTORY,
177		B_USER_DATA_DIRECTORY,
178		B_SYSTEM_NONPACKAGED_DATA_DIRECTORY,
179		B_SYSTEM_DATA_DIRECTORY
180	};
181
182	status_t status = B_ENTRY_NOT_FOUND;
183
184	for (size_t i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
185		BPath path;
186		if (find_directory(which[i], &path) == B_OK) {
187			BString catalogName(path.Path());
188			catalogName << "/locale/" << kCatFolder
189				<< "/" << fSignature
190				<< "/" << fLanguageName
191				<< kCatExtension;
192			status = ReadFromFile(catalogName.String());
193			if (status == B_OK)
194				break;
195		}
196	}
197
198	return status;
199}
200
201
202status_t
203DefaultCatalog::ReadFromFile(const char *path)
204{
205	if (!path)
206		path = fPath.String();
207
208	BFile catalogFile;
209	status_t res = catalogFile.SetTo(path, B_READ_ONLY);
210	if (res != B_OK)
211		return B_ENTRY_NOT_FOUND;
212
213	fPath = path;
214
215	off_t sz = 0;
216	res = catalogFile.GetSize(&sz);
217	if (res != B_OK) {
218		return res;
219	}
220
221	BStackOrHeapArray<char, 0> buf(sz);
222	if (!buf.IsValid())
223		return B_NO_MEMORY;
224	res = catalogFile.Read(buf, sz);
225	if (res < B_OK)
226		return res;
227	if (res < sz)
228		return res;
229	BMemoryIO memIO(buf, sz);
230	res = Unflatten(&memIO);
231
232	if (res == B_OK) {
233		// some information living in member variables needs to be copied
234		// to attributes. Although these attributes should have been written
235		// when creating the catalog, we make sure that they exist there:
236		UpdateAttributes(catalogFile);
237	}
238
239	return res;
240}
241
242
243status_t
244DefaultCatalog::ReadFromResource(const entry_ref &appOrAddOnRef)
245{
246	BFile file;
247	status_t res = file.SetTo(&appOrAddOnRef, B_READ_ONLY);
248	if (res != B_OK)
249		return B_ENTRY_NOT_FOUND;
250
251	BResources rsrc;
252	res = rsrc.SetTo(&file);
253	if (res != B_OK)
254		return res;
255
256	size_t sz;
257	const void *buf = rsrc.LoadResource('CADA', fLanguageName, &sz);
258	if (!buf)
259		return B_NAME_NOT_FOUND;
260
261	BMemoryIO memIO(buf, sz);
262	res = Unflatten(&memIO);
263
264	return res;
265}
266
267
268status_t
269DefaultCatalog::WriteToFile(const char *path)
270{
271	BFile catalogFile;
272	if (path)
273		fPath = path;
274	status_t res = catalogFile.SetTo(fPath.String(),
275		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
276	if (res != B_OK)
277		return res;
278
279	BMallocIO mallocIO;
280	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, (int32)256));
281		// set a largish block-size in order to avoid reallocs
282	res = Flatten(&mallocIO);
283	if (res == B_OK) {
284		ssize_t wsz;
285		wsz = catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength());
286		if (wsz != (ssize_t)mallocIO.BufferLength())
287			return B_FILE_ERROR;
288
289		// set mimetype-, language- and signature-attributes:
290		UpdateAttributes(catalogFile);
291	}
292	if (res == B_OK)
293		UpdateAttributes(catalogFile);
294	return res;
295}
296
297
298status_t
299DefaultCatalog::WriteToResource(const entry_ref &appOrAddOnRef)
300{
301	BFile file;
302	status_t res = file.SetTo(&appOrAddOnRef, B_READ_WRITE);
303	if (res != B_OK)
304		return res;
305
306	BResources rsrc;
307	res = rsrc.SetTo(&file);
308	if (res != B_OK)
309		return res;
310
311	BMallocIO mallocIO;
312	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, (int32)256));
313		// set a largish block-size in order to avoid reallocs
314	res = Flatten(&mallocIO);
315
316	int mangledLanguage = CatKey::HashFun(fLanguageName.String(), 0);
317
318	if (res == B_OK) {
319		res = rsrc.AddResource('CADA', mangledLanguage,
320			mallocIO.Buffer(), mallocIO.BufferLength(),
321			BString(fLanguageName));
322	}
323
324	return res;
325}
326
327
328/*!	Writes mimetype, language-name and signature of catalog into the
329	catalog-file.
330*/
331void
332DefaultCatalog::UpdateAttributes(BFile& catalogFile)
333{
334	static const int bufSize = 256;
335	char buf[bufSize];
336	uint32 temp;
337	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf,
338			bufSize) <= 0
339		|| strcmp(kCatMimeType, buf) != 0) {
340		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
341			kCatMimeType, strlen(kCatMimeType)+1);
342	}
343	if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
344			&buf, bufSize) <= 0
345		|| fLanguageName != buf) {
346		catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
347			fLanguageName.String(), fLanguageName.Length()+1);
348	}
349	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
350			&buf, bufSize) <= 0
351		|| fSignature != buf) {
352		catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
353			fSignature.String(), fSignature.Length()+1);
354	}
355	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
356		0, &temp, sizeof(uint32)) <= 0) {
357		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
358			0, &fFingerprint, sizeof(uint32));
359	}
360}
361
362
363status_t
364DefaultCatalog::Flatten(BDataIO *dataIO)
365{
366	UpdateFingerprint();
367		// make sure we have the correct fingerprint before we flatten it
368
369	status_t res;
370	BMessage archive;
371	int32 count = fCatMap.Size();
372	res = archive.AddString("class", "DefaultCatalog");
373	if (res == B_OK)
374		res = archive.AddInt32("c:sz", count);
375	if (res == B_OK)
376		res = archive.AddInt16("c:ver", kCatArchiveVersion);
377	if (res == B_OK)
378		res = archive.AddString("c:lang", fLanguageName.String());
379	if (res == B_OK)
380		res = archive.AddString("c:sig", fSignature.String());
381	if (res == B_OK)
382		res = archive.AddInt32("c:fpr", fFingerprint);
383	if (res == B_OK)
384		res = archive.Flatten(dataIO);
385
386	CatMap::Iterator iter = fCatMap.GetIterator();
387	CatMap::Entry entry;
388	while (res == B_OK && iter.HasNext()) {
389		entry = iter.Next();
390		archive.MakeEmpty();
391		res = archive.AddString("c:ostr", entry.key.fString.String());
392		if (res == B_OK)
393			res = archive.AddString("c:ctxt", entry.key.fContext.String());
394		if (res == B_OK)
395			res = archive.AddString("c:comt", entry.key.fComment.String());
396		if (res == B_OK)
397			res = archive.AddInt32("c:hash", entry.key.fHashVal);
398		if (res == B_OK)
399			res = archive.AddString("c:tstr", entry.value.String());
400		if (res == B_OK)
401			res = archive.Flatten(dataIO);
402	}
403
404	return res;
405}
406
407
408status_t
409DefaultCatalog::Unflatten(BDataIO *dataIO)
410{
411	fCatMap.Clear();
412	int32 count = 0;
413	int16 version;
414	BMessage archiveMsg;
415	status_t res = archiveMsg.Unflatten(dataIO);
416
417	if (res == B_OK) {
418		res = archiveMsg.FindInt16("c:ver", &version)
419			|| archiveMsg.FindInt32("c:sz", &count);
420	}
421	if (res == B_OK) {
422		fLanguageName = archiveMsg.FindString("c:lang");
423		fSignature = archiveMsg.FindString("c:sig");
424		uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr");
425
426		// if a specific fingerprint has been requested and the catalog does in
427		// fact have a fingerprint, both are compared. If they mismatch, we do
428		// not accept this catalog:
429		if (foundFingerprint != 0 && fFingerprint != 0
430			&& foundFingerprint != fFingerprint) {
431			res = B_MISMATCHED_VALUES;
432		} else
433			fFingerprint = foundFingerprint;
434	}
435
436	if (res == B_OK && count > 0) {
437		CatKey key;
438		const char *keyStr;
439		const char *keyCtx;
440		const char *keyCmt;
441		const char *translated;
442
443		// fCatMap.resize(count);
444			// There is no resize method in Haiku's HashMap to preallocate
445			// memory.
446		for (int i=0; res == B_OK && i < count; ++i) {
447			res = archiveMsg.Unflatten(dataIO);
448			if (res == B_OK)
449				res = archiveMsg.FindString("c:ostr", &keyStr);
450			if (res == B_OK)
451				res = archiveMsg.FindString("c:ctxt", &keyCtx);
452			if (res == B_OK)
453				res = archiveMsg.FindString("c:comt", &keyCmt);
454			if (res == B_OK)
455				res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal);
456			if (res == B_OK)
457				res = archiveMsg.FindString("c:tstr", &translated);
458			if (res == B_OK) {
459				key.fString = keyStr;
460				key.fContext = keyCtx;
461				key.fComment = keyCmt;
462				fCatMap.Put(key, translated);
463			}
464		}
465		uint32 checkFP = ComputeFingerprint();
466		if (fFingerprint != checkFP)
467			return B_BAD_DATA;
468	}
469	return res;
470}
471
472
473BCatalogData *
474DefaultCatalog::Instantiate(const entry_ref &catalogOwner, const char *language,
475	uint32 fingerprint)
476{
477	DefaultCatalog *catalog
478		= new(std::nothrow) DefaultCatalog(catalogOwner, language, fingerprint);
479	if (catalog && catalog->InitCheck() != B_OK) {
480		delete catalog;
481		return NULL;
482	}
483	return catalog;
484}
485
486
487BCatalogData *
488DefaultCatalog::Create(const char *signature, const char *language)
489{
490	DefaultCatalog *catalog
491		= new(std::nothrow) DefaultCatalog("", signature, language);
492	if (catalog && catalog->InitCheck() != B_OK) {
493		delete catalog;
494		return NULL;
495	}
496	return catalog;
497}
498
499
500} // namespace BPrivate
501
502
503extern "C" status_t
504default_catalog_get_available_languages(BMessage* availableLanguages,
505	const char* sigPattern, const char* langPattern, int32 fingerprint)
506{
507	if (availableLanguages == NULL || sigPattern == NULL)
508		return B_BAD_DATA;
509
510	app_info appInfo;
511	be_app->GetAppInfo(&appInfo);
512	node_ref nref;
513	nref.device = appInfo.ref.device;
514	nref.node = appInfo.ref.directory;
515	BDirectory appDir(&nref);
516	BString catalogName("locale/");
517	catalogName << kCatFolder
518		<< "/" << sigPattern ;
519	BPath catalogPath(&appDir, catalogName.String());
520	BEntry file(catalogPath.Path());
521	BDirectory dir(&file);
522
523	char fileName[B_FILE_NAME_LENGTH];
524	while(dir.GetNextEntry(&file) == B_OK) {
525		file.GetName(fileName);
526		BString langName(fileName);
527		langName.Replace(kCatExtension, "", 1);
528		availableLanguages->AddString("language", langName);
529	}
530
531	// search in data folders
532
533	directory_which which[] = {
534		B_USER_NONPACKAGED_DATA_DIRECTORY,
535		B_USER_DATA_DIRECTORY,
536		B_SYSTEM_NONPACKAGED_DATA_DIRECTORY,
537		B_SYSTEM_DATA_DIRECTORY
538	};
539
540	for (size_t i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
541		BPath path;
542		if (find_directory(which[i], &path) == B_OK) {
543			catalogName = BString("locale/")
544				<< kCatFolder
545				<< "/" << sigPattern;
546
547			BPath catalogPath(path.Path(), catalogName.String());
548			BEntry file(catalogPath.Path());
549			BDirectory dir(&file);
550
551			char fileName[B_FILE_NAME_LENGTH];
552			while(dir.GetNextEntry(&file) == B_OK) {
553				file.GetName(fileName);
554				BString langName(fileName);
555				langName.Replace(kCatExtension, "", 1);
556				availableLanguages->AddString("language", langName);
557			}
558		}
559	}
560
561	return B_OK;
562}
563