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