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