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