xref: /haiku/src/kits/locale/DefaultCatalog.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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, "no catalog at %s", path);
164 		return B_ENTRY_NOT_FOUND;
165 	}
166 
167 	fPath = path;
168 	log_team(LOG_DEBUG, "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, "couldn't get size for catalog-file %s", path);
174 		return res;
175 	}
176 
177 	auto_ptr<char> buf(new(std::nothrow) char [sz]);
178 	if (buf.get() == NULL) {
179 		log_team(LOG_ERR, "couldn't allocate array of %d chars", sz);
180 		return B_NO_MEMORY;
181 	}
182 	res = catalogFile.Read(buf.get(), sz);
183 	if (res < B_OK) {
184 		log_team(LOG_ERR, "couldn't read from catalog-file %s", path);
185 		return res;
186 	}
187 	if (res < sz) {
188 		log_team(LOG_ERR,
189 			"only got %lu instead of %Lu bytes from catalog-file %s", res, sz,
190 			path);
191 		return res;
192 	}
193 	BMemoryIO memIO(buf.get(), sz);
194 	res = Unflatten(&memIO);
195 
196 	if (res == B_OK) {
197 		// some information living in member variables needs to be copied
198 		// to attributes. Although these attributes should have been written
199 		// when creating the catalog, we make sure that they exist there:
200 		UpdateAttributes(catalogFile);
201 	}
202 
203 	return res;
204 }
205 
206 
207 /*
208  * this method is not currently being used, but it may be useful in the future...
209  */
210 status_t
211 DefaultCatalog::ReadFromAttribute(entry_ref *appOrAddOnRef)
212 {
213 	BNode node;
214 	status_t res = node.SetTo(appOrAddOnRef);
215 	if (res != B_OK) {
216 		log_team(LOG_ERR,
217 			"couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)",
218 			appOrAddOnRef->device, appOrAddOnRef->directory,
219 			appOrAddOnRef->name);
220 		return B_ENTRY_NOT_FOUND;
221 	}
222 
223 	log_team(LOG_DEBUG,
224 		"looking for embedded catalog-attribute in app/add-on"
225 		"(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef->device,
226 		appOrAddOnRef->directory, appOrAddOnRef->name);
227 
228 	attr_info attrInfo;
229 	res = node.GetAttrInfo(BLocaleRoster::kEmbeddedCatAttr, &attrInfo);
230 	if (res != B_OK) {
231 		log_team(LOG_DEBUG, "no embedded catalog found");
232 		return B_NAME_NOT_FOUND;
233 	}
234 	if (attrInfo.type != B_MESSAGE_TYPE) {
235 		log_team(LOG_ERR, "attribute %s has incorrect type and is ignored!",
236 			BLocaleRoster::kEmbeddedCatAttr);
237 		return B_BAD_TYPE;
238 	}
239 
240 	size_t size = attrInfo.size;
241 	auto_ptr<char> buf(new(std::nothrow) char [size]);
242 	if (buf.get() == NULL) {
243 		log_team(LOG_ERR, "couldn't allocate array of %d chars", size);
244 		return B_NO_MEMORY;
245 	}
246 	res = node.ReadAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0,
247 		buf.get(), size);
248 	if (res < (ssize_t)size) {
249 		log_team(LOG_ERR, "unable to read embedded catalog from attribute");
250 		return res < B_OK ? res : B_BAD_DATA;
251 	}
252 
253 	BMemoryIO memIO(buf.get(), size);
254 	res = Unflatten(&memIO);
255 
256 	return res;
257 }
258 
259 
260 status_t
261 DefaultCatalog::ReadFromResource(entry_ref *appOrAddOnRef)
262 {
263 	BFile file;
264 	status_t res = file.SetTo(appOrAddOnRef, B_READ_ONLY);
265 	if (res != B_OK) {
266 		log_team(LOG_ERR,
267 			"couldn't find app or add-on (dev=%lu, dir=%Lu, name=%s)",
268 			appOrAddOnRef->device, appOrAddOnRef->directory,
269 			appOrAddOnRef->name);
270 		return B_ENTRY_NOT_FOUND;
271 	}
272 
273 	log_team(LOG_DEBUG,
274 		"looking for embedded catalog-resource in app/add-on"
275 		"(dev=%lu, dir=%Lu, name=%s)", appOrAddOnRef->device,
276 		appOrAddOnRef->directory, appOrAddOnRef->name);
277 
278 	BResources rsrc;
279 	res = rsrc.SetTo(&file);
280 	if (res != B_OK) {
281 		log_team(LOG_DEBUG, "file has no resources");
282 		return res;
283 	}
284 
285 	size_t sz;
286 	const void *buf = rsrc.LoadResource(B_MESSAGE_TYPE,
287 		BLocaleRoster::kEmbeddedCatResId, &sz);
288 	if (!buf) {
289 		log_team(LOG_DEBUG, "file has no catalog-resource");
290 		return B_NAME_NOT_FOUND;
291 	}
292 
293 	BMemoryIO memIO(buf, sz);
294 	res = Unflatten(&memIO);
295 
296 	return res;
297 }
298 
299 
300 status_t
301 DefaultCatalog::WriteToFile(const char *path)
302 {
303 	BFile catalogFile;
304 	if (path)
305 		fPath = path;
306 	status_t res = catalogFile.SetTo(fPath.String(),
307 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
308 	if (res != B_OK)
309 		return res;
310 
311 	BMallocIO mallocIO;
312 	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L));
313 		// set a largish block-size in order to avoid reallocs
314 	res = Flatten(&mallocIO);
315 	if (res == B_OK) {
316 		ssize_t wsz;
317 		wsz = catalogFile.Write(mallocIO.Buffer(), mallocIO.BufferLength());
318 		if (wsz != (ssize_t)mallocIO.BufferLength())
319 			return B_FILE_ERROR;
320 
321 		// set mimetype-, language- and signature-attributes:
322 		UpdateAttributes(catalogFile);
323 	}
324 	if (res == B_OK)
325 		UpdateAttributes(catalogFile);
326 	return res;
327 }
328 
329 
330 /*
331  * this method is not currently being used, but it may be useful in the
332  * future...
333  */
334 status_t
335 DefaultCatalog::WriteToAttribute(entry_ref *appOrAddOnRef)
336 {
337 	BNode node;
338 	status_t res = node.SetTo(appOrAddOnRef);
339 	if (res != B_OK)
340 		return res;
341 
342 	BMallocIO mallocIO;
343 	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L));
344 		// set a largish block-size in order to avoid reallocs
345 	res = Flatten(&mallocIO);
346 
347 	if (res == B_OK) {
348 		ssize_t wsz;
349 		wsz = node.WriteAttr(BLocaleRoster::kEmbeddedCatAttr, B_MESSAGE_TYPE, 0,
350 			mallocIO.Buffer(), mallocIO.BufferLength());
351 		if (wsz < B_OK)
352 			res = wsz;
353 		else if (wsz != (ssize_t)mallocIO.BufferLength())
354 			res = B_ERROR;
355 	}
356 	return res;
357 }
358 
359 
360 status_t
361 DefaultCatalog::WriteToResource(entry_ref *appOrAddOnRef)
362 {
363 	BFile file;
364 	status_t res = file.SetTo(appOrAddOnRef, B_READ_WRITE);
365 	if (res != B_OK)
366 		return res;
367 
368 	BResources rsrc;
369 	res = rsrc.SetTo(&file);
370 	if (res != B_OK)
371 		return res;
372 
373 	BMallocIO mallocIO;
374 	mallocIO.SetBlockSize(max(fCatMap.Size() * 20, 256L));
375 		// set a largish block-size in order to avoid reallocs
376 	res = Flatten(&mallocIO);
377 
378 	if (res == B_OK) {
379 		res = rsrc.AddResource(B_MESSAGE_TYPE, BLocaleRoster::kEmbeddedCatResId,
380 			mallocIO.Buffer(), mallocIO.BufferLength(), "embedded catalog");
381 	}
382 
383 	return res;
384 }
385 
386 
387 /*
388  * writes mimetype, language-name and signature of catalog into the
389  * catalog-file.
390  */
391 void
392 DefaultCatalog::UpdateAttributes(BFile& catalogFile)
393 {
394 	static const int bufSize = 256;
395 	char buf[bufSize];
396 	uint32 temp;
397 	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf,
398 			bufSize) <= 0
399 		|| strcmp(kCatMimeType, buf) != 0) {
400 		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
401 			kCatMimeType, strlen(kCatMimeType)+1);
402 	}
403 	if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
404 			&buf, bufSize) <= 0
405 		|| fLanguageName != buf) {
406 		catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
407 			fLanguageName.String(), fLanguageName.Length()+1);
408 	}
409 	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
410 			&buf, bufSize) <= 0
411 		|| fSignature != buf) {
412 		catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
413 			fSignature.String(), fSignature.Length()+1);
414 	}
415 	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
416 		0, &temp, sizeof(uint32)) <= 0) {
417 		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
418 			0, &fFingerprint, sizeof(uint32));
419 	}
420 }
421 
422 
423 status_t
424 DefaultCatalog::Flatten(BDataIO *dataIO)
425 {
426 	UpdateFingerprint();
427 		// make sure we have the correct fingerprint before we flatten it
428 
429 	status_t res;
430 	BMessage archive;
431 	int32 count = fCatMap.Size();
432 	res = archive.AddString("class", "DefaultCatalog");
433 	if (res == B_OK)
434 		res = archive.AddInt32("c:sz", count);
435 	if (res == B_OK)
436 		res = archive.AddInt16("c:ver", kCatArchiveVersion);
437 	if (res == B_OK)
438 		res = archive.AddString("c:lang", fLanguageName.String());
439 	if (res == B_OK)
440 		res = archive.AddString("c:sig", fSignature.String());
441 	if (res == B_OK)
442 		res = archive.AddInt32("c:fpr", fFingerprint);
443 	if (res == B_OK)
444 		res = archive.Flatten(dataIO);
445 
446 	CatMap::Iterator iter = fCatMap.GetIterator();
447 	CatMap::Entry entry;
448 	while (res == B_OK && iter.HasNext()) {
449 		entry = iter.Next();
450 		archive.MakeEmpty();
451 		res = archive.AddString("c:ostr", entry.key.fString.String());
452 		if (res == B_OK)
453 			res = archive.AddString("c:ctxt", entry.key.fContext.String());
454 		if (res == B_OK)
455 			res = archive.AddString("c:comt", entry.key.fComment.String());
456 		if (res == B_OK)
457 			res = archive.AddInt32("c:hash", entry.key.fHashVal);
458 		if (res == B_OK)
459 			res = archive.AddString("c:tstr", entry.value.String());
460 		if (res == B_OK)
461 			res = archive.Flatten(dataIO);
462 	}
463 
464 	return res;
465 }
466 
467 
468 status_t
469 DefaultCatalog::Unflatten(BDataIO *dataIO)
470 {
471 	fCatMap.Clear();
472 	int32 count = 0;
473 	int16 version;
474 	BMessage archiveMsg;
475 	status_t res = archiveMsg.Unflatten(dataIO);
476 
477 	if (res == B_OK) {
478 		res = archiveMsg.FindInt16("c:ver", &version)
479 			|| archiveMsg.FindInt32("c:sz", &count);
480 	}
481 	if (res == B_OK) {
482 		fLanguageName = archiveMsg.FindString("c:lang");
483 		fSignature = archiveMsg.FindString("c:sig");
484 		uint32 foundFingerprint = archiveMsg.FindInt32("c:fpr");
485 
486 		// if a specific fingerprint has been requested and the catalog does in
487 		// fact have a fingerprint, both are compared. If they mismatch, we do
488 		// not accept this catalog:
489 		if (foundFingerprint != 0 && fFingerprint != 0
490 			&& foundFingerprint != fFingerprint) {
491 			log_team(LOG_INFO, "default-catalog(sig=%s, lang=%s) "
492 				"has mismatching fingerprint (%ld instead of the requested %ld), "
493 				"so this catalog is skipped.",
494 				fSignature.String(), fLanguageName.String(), foundFingerprint,
495 				fFingerprint);
496 			res = B_MISMATCHED_VALUES;
497 		} else
498 			fFingerprint = foundFingerprint;
499 	}
500 
501 	if (res == B_OK && count > 0) {
502 		CatKey key;
503 		const char *keyStr;
504 		const char *keyCtx;
505 		const char *keyCmt;
506 		const char *translated;
507 
508 		// fCatMap.resize(count);
509 			// There is no resize method in Haiku's HashMap to preallocate
510 			// memory.
511 		for (int i=0; res == B_OK && i < count; ++i) {
512 			res = archiveMsg.Unflatten(dataIO);
513 			if (res == B_OK)
514 				res = archiveMsg.FindString("c:ostr", &keyStr);
515 			if (res == B_OK)
516 				res = archiveMsg.FindString("c:ctxt", &keyCtx);
517 			if (res == B_OK)
518 				res = archiveMsg.FindString("c:comt", &keyCmt);
519 			if (res == B_OK)
520 				res = archiveMsg.FindInt32("c:hash", (int32*)&key.fHashVal);
521 			if (res == B_OK)
522 				res = archiveMsg.FindString("c:tstr", &translated);
523 			if (res == B_OK) {
524 				key.fString = keyStr;
525 				key.fContext = keyCtx;
526 				key.fComment = keyCmt;
527 				fCatMap.Put(key, translated);
528 			}
529 		}
530 		uint32 checkFP = ComputeFingerprint();
531 		if (fFingerprint != checkFP) {
532 			log_team(LOG_WARNING, "default-catalog(sig=%s, lang=%s) "
533 				"has wrong fingerprint after load (%ld instead of the %ld). "
534 				"The catalog data may be corrupted, so this catalog is skipped.",
535 				fSignature.String(), fLanguageName.String(), checkFP,
536 				fFingerprint);
537 			return B_BAD_DATA;
538 		}
539 	}
540 	return res;
541 }
542 
543 
544 BCatalogAddOn *
545 DefaultCatalog::Instantiate(const char *signature, const char *language,
546 	uint32 fingerprint)
547 {
548 	DefaultCatalog *catalog
549 		= new(std::nothrow) DefaultCatalog(signature, language, fingerprint);
550 	if (catalog && catalog->InitCheck() != B_OK) {
551 		delete catalog;
552 		return NULL;
553 	}
554 	return catalog;
555 }
556 
557 
558 BCatalogAddOn *
559 DefaultCatalog::InstantiateEmbedded(entry_ref *appOrAddOnRef)
560 {
561 	DefaultCatalog *catalog = new(std::nothrow) DefaultCatalog(appOrAddOnRef);
562 	if (catalog && catalog->InitCheck() != B_OK) {
563 		delete catalog;
564 		return NULL;
565 	}
566 	return catalog;
567 }
568 
569 
570 BCatalogAddOn *
571 DefaultCatalog::Create(const char *signature, const char *language)
572 {
573 	DefaultCatalog *catalog
574 		= new(std::nothrow) DefaultCatalog("", signature, language);
575 	if (catalog && catalog->InitCheck() != B_OK) {
576 		delete catalog;
577 		return NULL;
578 	}
579 	return catalog;
580 }
581 
582 
583 const uint8 DefaultCatalog::kDefaultCatalogAddOnPriority = 1;
584 	// give highest priority to our embedded catalog-add-on
585