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