xref: /haiku/src/add-ons/locale/catalogs/plaintext/Catalog.cpp (revision df4074fbed092b09474695aa286752ca41625332)
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 || fLanguageName != buf) {
358 		catalogFile.WriteAttrString(BLocaleRoster::kCatLangAttr, &fLanguageName);
359 	}
360 	if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
361 			&buf, bufSize) <= 0 || fSignature != buf) {
362 		catalogFile.WriteAttrString(BLocaleRoster::kCatSigAttr, &fSignature);
363 	}
364 	if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
365 		0, &temp, sizeof(uint32)) <= 0) {
366 		catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
367 			0, &fFingerprint, sizeof(uint32));
368 	}
369 }
370 
371 
372 void
373 PlainTextCatalog::UpdateAttributes(const char* path)
374 {
375 	BEntry entry(path);
376 	BFile node(&entry, B_READ_WRITE);
377 	UpdateAttributes(node);
378 }
379 
380 
381 BCatalogData *
382 PlainTextCatalog::Instantiate(const entry_ref& owner, const char *language,
383 	uint32 fingerprint)
384 {
385 	PlainTextCatalog *catalog
386 		= new(std::nothrow) PlainTextCatalog(owner, language, fingerprint);
387 	if (catalog && catalog->InitCheck() != B_OK) {
388 		delete catalog;
389 		return NULL;
390 	}
391 	return catalog;
392 }
393 
394 
395 extern "C" BCatalogData *
396 instantiate_catalog(const entry_ref& owner, const char *language,
397 	uint32 fingerprint)
398 {
399 	PlainTextCatalog *catalog
400 		= new(std::nothrow) PlainTextCatalog(owner, language, fingerprint);
401 	if (catalog && catalog->InitCheck() != B_OK) {
402 		delete catalog;
403 		return NULL;
404 	}
405 	return catalog;
406 }
407 
408 
409 extern "C" BCatalogData *
410 create_catalog(const char *signature, const char *language)
411 {
412 	PlainTextCatalog *catalog
413 		= new(std::nothrow) PlainTextCatalog("emptycat", signature, language);
414 	return catalog;
415 }
416 
417 
418 extern "C" status_t
419 get_available_languages(BMessage* availableLanguages,
420 	const char* sigPattern = NULL, const char* langPattern = NULL,
421 	int32 fingerprint = 0)
422 {
423 	// TODO
424 	return B_ERROR;
425 }
426 
427 
428 uint8 gCatalogAddOnPriority = 99;
429 	// give low priority to this add on, we don't want it to be actually used
430