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