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