xref: /haiku/src/add-ons/locale/catalogs/plaintext/Catalog.cpp (revision 6439130107c50e929f9c195b8afc4994a9fd8cbf)
1 /*
2  * Copyright 2009 Adrien Destugues, pulkomandy@gmail.com. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <PlainTextCatalog.h>
7 
8 #include <assert.h>
9 #include <cstdio>
10 #include <iostream>
11 #include <fstream>
12 #include <new>
13 #include <sstream>
14 #include <string>
15 
16 #include <AppFileInfo.h>
17 #include <Application.h>
18 #include <Directory.h>
19 #include <File.h>
20 #include <FindDirectory.h>
21 #include <fs_attr.h>
22 #include <Message.h>
23 #include <Mime.h>
24 #include <Path.h>
25 #include <Resources.h>
26 #include <Roster.h>
27 #include <String.h>
28 
29 #include <LocaleRoster.h>
30 #include <Catalog.h>
31 
32 
33 using BPrivate::HashMapCatalog;
34 using BPrivate::PlainTextCatalog;
35 using std::min;
36 using std::max;
37 using std::pair;
38 
39 
40 /*
41  *	This file implements the plain text catalog-type for the Haiku
42  *	locale kit. It is not meant to be used in applications like other add ons,
43  *	but only as a way to get an easy to translate file for developers.
44  */
45 
46 
47 extern "C" uint32 adler32(uint32 adler, const uint8 *buf, uint32 len);
48 	// definition lives in adler32.c
49 
50 static const char *kCatFolder = "catalogs";
51 static const char *kCatExtension = ".catkeys";
52 
53 const char *PlainTextCatalog::kCatMimeType
54 	= "locale/x-vnd.Be.locale-catalog.plaintext";
55 
56 static int16 kCatArchiveVersion = 1;
57 	// version of the catalog archive structure, bump this if you change it!
58 
59 
60 void
escapeQuotedChars(BString & stringToEscape)61 escapeQuotedChars(BString& stringToEscape)
62 {
63 	stringToEscape.ReplaceAll("\\","\\\\");
64 	stringToEscape.ReplaceAll("\n","\\n");
65 	stringToEscape.ReplaceAll("\t","\\t");
66 	stringToEscape.ReplaceAll("\"","\\\"");
67 }
68 
69 
70 /*
71  * constructs a PlainTextCatalog with given signature and language and reads
72  * the catalog from disk.
73  * InitCheck() will be B_OK if catalog could be loaded successfully, it will
74  * give an appropriate error-code otherwise.
75  */
PlainTextCatalog(const entry_ref & owner,const char * language,uint32 fingerprint)76 PlainTextCatalog::PlainTextCatalog(const entry_ref &owner, const char *language,
77 	uint32 fingerprint)
78 	:
79 	HashMapCatalog("", language, fingerprint)
80 {
81 	// We created the catalog with an invalid signature, but we fix that now.
82 	SetSignature(owner);
83 
84 	// give highest priority to catalog living in sub-folder of app's folder:
85 	app_info appInfo;
86 	be_app->GetAppInfo(&appInfo);
87 	node_ref nref;
88 	nref.device = appInfo.ref.device;
89 	nref.node = appInfo.ref.directory;
90 	BDirectory appDir(&nref);
91 	BString catalogName("locale/");
92 	catalogName << kCatFolder
93 		<< "/" << fSignature
94 		<< "/" << fLanguageName
95 		<< kCatExtension;
96 	BPath catalogPath(&appDir, catalogName.String());
97 	status_t status = ReadFromFile(catalogPath.Path());
98 
99 	if (status != B_OK) {
100 		// look in common-etc folder (/boot/home/config/etc):
101 		BPath commonEtcPath;
102 		find_directory(B_SYSTEM_ETC_DIRECTORY, &commonEtcPath);
103 		if (commonEtcPath.InitCheck() == B_OK) {
104 			catalogName = BString(commonEtcPath.Path())
105 							<< "/locale/" << kCatFolder
106 							<< "/" << fSignature
107 							<< "/" << fLanguageName
108 							<< kCatExtension;
109 			status = ReadFromFile(catalogName.String());
110 		}
111 	}
112 
113 	if (status != B_OK) {
114 		// look in system-etc folder (/boot/beos/etc):
115 		BPath systemEtcPath;
116 		find_directory(B_BEOS_ETC_DIRECTORY, &systemEtcPath);
117 		if (systemEtcPath.InitCheck() == B_OK) {
118 			catalogName = BString(systemEtcPath.Path())
119 							<< "/locale/" << kCatFolder
120 							<< "/" << fSignature
121 							<< "/" << fLanguageName
122 							<< kCatExtension;
123 			status = ReadFromFile(catalogName.String());
124 		}
125 	}
126 
127 	fInitCheck = status;
128 }
129 
130 
131 /*
132  * constructs an empty PlainTextCatalog with given sig and language.
133  * This is used for editing/testing purposes.
134  * InitCheck() will always be B_OK.
135  */
PlainTextCatalog(const char * path,const char * signature,const char * language)136 PlainTextCatalog::PlainTextCatalog(const char *path, const char *signature,
137 	const char *language)
138 	:
139 	HashMapCatalog(signature, language, 0),
140 	fPath(path)
141 {
142 	fInitCheck = B_OK;
143 }
144 
145 
~PlainTextCatalog()146 PlainTextCatalog::~PlainTextCatalog()
147 {
148 }
149 
150 
151 void
SetSignature(const entry_ref & catalogOwner)152 PlainTextCatalog::SetSignature(const entry_ref &catalogOwner)
153 {
154 	// figure out mimetype from image
155 	BFile objectFile(&catalogOwner, B_READ_ONLY);
156 	BAppFileInfo objectInfo(&objectFile);
157 	char objectSignature[B_MIME_TYPE_LENGTH];
158 	if (objectInfo.GetSignature(objectSignature) != B_OK) {
159 		fSignature = "";
160 		return;
161 	}
162 
163 	// drop supertype from mimetype (should be "application/"):
164 	char* stripSignature = objectSignature;
165 	while (*stripSignature != '/' && *stripSignature != '\0')
166 		stripSignature ++;
167 
168 	if (*stripSignature == '\0')
169 		stripSignature = objectSignature;
170 	else
171 		stripSignature ++;
172 
173 	fSignature = stripSignature;
174 }
175 
176 
177 status_t
ReadFromFile(const char * path)178 PlainTextCatalog::ReadFromFile(const char *path)
179 {
180 	std::fstream catalogFile;
181 	std::string currentItem;
182 
183 	if (!path)
184 		path = fPath.String();
185 
186 	catalogFile.open(path, std::fstream::in);
187 	if (!catalogFile.is_open())
188 		return B_ENTRY_NOT_FOUND;
189 
190 	// Now read all the data from the file
191 
192 	// The first line holds some info about the catalog :
193 	// ArchiveVersion \t LanguageName \t Signature \t FingerPrint
194 	if (std::getline(catalogFile, currentItem, '\t').good()) {
195 		// Get the archive version
196 		int arcver= -1;
197 		std::istringstream ss(currentItem);
198 		ss >> arcver;
199 		if (ss.fail()) {
200 			// can't convert to int
201 			return B_ERROR;
202 		}
203 
204 		if (arcver != kCatArchiveVersion) {
205 			// wrong version
206 			return B_ERROR;
207 		}
208 	} else
209 		return B_ERROR;
210 
211 	if (std::getline(catalogFile, currentItem, '\t').good()) {
212 		// Get the language
213 		fLanguageName = currentItem.c_str() ;
214 	} else
215 		return B_ERROR;
216 
217 	if (std::getline(catalogFile, currentItem, '\t').good()) {
218 		// Get the signature
219 		fSignature = currentItem.c_str() ;
220 	} else
221 		return B_ERROR;
222 
223 	if (std::getline(catalogFile, currentItem).good()) {
224 		// Get the fingerprint
225 		std::istringstream ss(currentItem);
226 		uint32 foundFingerprint;
227 		ss >> foundFingerprint;
228 		if (ss.fail())
229 			return B_ERROR;
230 
231 		if (fFingerprint == 0)
232 			fFingerprint = foundFingerprint;
233 
234 		if (fFingerprint != foundFingerprint) {
235 			return B_MISMATCHED_VALUES;
236 		}
237 	} else
238 		return B_ERROR;
239 
240 	// We managed to open the file, so we remember it's the one we are using
241 	fPath = path;
242 
243 	std::string originalString;
244 	std::string context;
245 	std::string comment;
246 	std::string translated;
247 
248 	while (std::getline(catalogFile, originalString,'\t').good()) {
249 		// Each line is : "original string \t context \t comment \t translation"
250 
251 		if (!std::getline(catalogFile, context,'\t').good())
252 			return B_ERROR;
253 
254 		if (!std::getline(catalogFile, comment,'\t').good())
255 			return B_ERROR;
256 
257 		if (!std::getline(catalogFile, translated).good())
258 			return B_ERROR;
259 
260 		// We could do that :
261 		// SetString(key, translated.c_str());
262 		// but we want to keep the strings in the new catkey. Hash collisions
263 		// happen, you know. (and CatKey::== will fail)
264 		SetString(originalString.c_str(), translated.c_str(), context.c_str(),
265 			comment.c_str());
266 	}
267 
268 	catalogFile.close();
269 
270 	uint32 checkFP = ComputeFingerprint();
271 	if (fFingerprint != checkFP)
272 		return B_BAD_DATA;
273 
274 	// some information living in member variables needs to be copied
275 	// to attributes. Although these attributes should have been written
276 	// when creating the catalog, we make sure that they exist there:
277 	UpdateAttributes(path);
278 	return B_OK;
279 }
280 
281 
282 status_t
WriteToFile(const char * path)283 PlainTextCatalog::WriteToFile(const char *path)
284 {
285 	BString textContent;
286 	BFile catalogFile;
287 
288 	if (path)
289 		fPath = path;
290 	status_t res = catalogFile.SetTo(fPath.String(),
291 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
292 	if (res != B_OK)
293 		return res;
294 
295 	UpdateFingerprint();
296 		// make sure we have the correct fingerprint before we flatten it
297 
298 	textContent << kCatArchiveVersion << "\t" << fLanguageName.String() << "\t"
299 		<< fSignature.String() << "\t" << fFingerprint << "\n";
300 
301 	res = catalogFile.Write(textContent.String(),textContent.Length());
302 	if (res != textContent.Length())
303 		return res;
304 
305 	CatMap::Iterator iter = fCatMap.GetIterator();
306 	CatMap::Entry entry;
307 	BString original;
308 	BString comment;
309 	BString translated;
310 
311 	while (iter.HasNext()) {
312 		entry = iter.Next();
313 		original = entry.key.fString;
314 		comment = entry.key.fComment;
315 		translated = entry.value;
316 
317 		escapeQuotedChars(original);
318 		escapeQuotedChars(comment);
319 		escapeQuotedChars(translated);
320 
321 		textContent.Truncate(0);
322 		textContent << original.String() << "\t"
323 			<< entry.key.fContext.String() << "\t"
324 			<< comment << "\t"
325 			<< translated.String() << "\n";
326 		res = catalogFile.Write(textContent.String(),textContent.Length());
327 		if (res != textContent.Length())
328 			return res;
329 	}
330 
331 	// set mimetype-, language- and signature-attributes:
332 	UpdateAttributes(catalogFile);
333 
334 	return B_OK;
335 }
336 
337 
338 /*
339  * writes mimetype, language-name and signature of catalog into the
340  * catalog-file.
341  */
342 void
UpdateAttributes(BFile & catalogFile)343 PlainTextCatalog::UpdateAttributes(BFile& catalogFile)
344 {
345 	static const int bufSize = 256;
346 	char buf[bufSize];
347 	uint32 temp;
348 	if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, bufSize)
349 			<= 0
350 		|| strcmp(kCatMimeType, buf) != 0) {
351 		catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
352 			kCatMimeType, strlen(kCatMimeType)+1);
353 	}
354 	if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
355 			&buf, bufSize) <= 0 || fLanguageName != buf) {
356 		catalogFile.WriteAttrString(BLocaleRoster::kCatLangAttr, &fLanguageName);
357 	}
358 	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
359 			&buf, bufSize) <= 0 || fSignature != buf) {
360 		catalogFile.WriteAttrString(BLocaleRoster::kCatSigAttr, &fSignature);
361 	}
362 	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
363 		0, &temp, sizeof(uint32)) <= 0) {
364 		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
365 			0, &fFingerprint, sizeof(uint32));
366 	}
367 }
368 
369 
370 void
UpdateAttributes(const char * path)371 PlainTextCatalog::UpdateAttributes(const char* path)
372 {
373 	BEntry entry(path);
374 	BFile node(&entry, B_READ_WRITE);
375 	UpdateAttributes(node);
376 }
377 
378 
379 BCatalogData *
Instantiate(const entry_ref & owner,const char * language,uint32 fingerprint)380 PlainTextCatalog::Instantiate(const entry_ref& owner, const char *language,
381 	uint32 fingerprint)
382 {
383 	PlainTextCatalog *catalog
384 		= new(std::nothrow) PlainTextCatalog(owner, language, fingerprint);
385 	if (catalog && catalog->InitCheck() != B_OK) {
386 		delete catalog;
387 		return NULL;
388 	}
389 	return catalog;
390 }
391 
392 
393 extern "C" BCatalogData *
instantiate_catalog(const entry_ref & owner,const char * language,uint32 fingerprint)394 instantiate_catalog(const entry_ref& owner, const char *language,
395 	uint32 fingerprint)
396 {
397 	PlainTextCatalog *catalog
398 		= new(std::nothrow) PlainTextCatalog(owner, language, fingerprint);
399 	if (catalog && catalog->InitCheck() != B_OK) {
400 		delete catalog;
401 		return NULL;
402 	}
403 	return catalog;
404 }
405 
406 
407 extern "C" BCatalogData *
create_catalog(const char * signature,const char * language)408 create_catalog(const char *signature, const char *language)
409 {
410 	PlainTextCatalog *catalog
411 		= new(std::nothrow) PlainTextCatalog("emptycat", signature, language);
412 	return catalog;
413 }
414 
415 
416 extern "C" status_t
get_available_languages(BMessage * availableLanguages,const char * sigPattern=NULL,const char * langPattern=NULL,int32 fingerprint=0)417 get_available_languages(BMessage* availableLanguages,
418 	const char* sigPattern = NULL, const char* langPattern = NULL,
419 	int32 fingerprint = 0)
420 {
421 	// TODO
422 	return B_ERROR;
423 }
424 
425 
426 uint8 gCatalogAddOnPriority = 99;
427 	// give low priority to this add on, we don't want it to be actually used
428