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