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