blob: 9633b89358225507e3bd7b0f3dc554e894b006f5 [file] [log] [blame]
Adrien Destugues374c6dc2015-01-11 16:37:56 +01001/*
Adrien Destugues31e4a022020-06-01 13:06:54 +02002** Copyright 2009-2020 Adrien Destugues, pulkomandy@pulkomandy.tk.
Adrien Destugues374c6dc2015-01-11 16:37:56 +01003** Distributed under the terms of the MIT License.
4*/
5
6#include "AmigaCatalog.h"
7
8#include <iostream>
9#include <memory>
10#include <new>
11
Adrien Destuguesbdc026b2019-06-23 09:53:53 +020012#include <arpa/inet.h>
Adrien Destugues31e4a022020-06-01 13:06:54 +020013#include <libgen.h>
Adrien Destuguesbdc026b2019-06-23 09:53:53 +020014
Adrien Destugues374c6dc2015-01-11 16:37:56 +010015#include <Application.h>
16#include <Directory.h>
17#include <File.h>
18#include <FindDirectory.h>
19#include <fs_attr.h>
Adrien Destuguesc102dfd2015-01-18 11:50:51 +010020#include <Language.h>
Adrien Destugues374c6dc2015-01-11 16:37:56 +010021#include <Mime.h>
22#include <Path.h>
23#include <Resources.h>
24#include <Roster.h>
25#include <StackOrHeapArray.h>
26#include <String.h>
Adrien Destuguesc102dfd2015-01-18 11:50:51 +010027#include <UTF8.h>
Adrien Destugues374c6dc2015-01-11 16:37:56 +010028
29#include <LocaleRoster.h>
30#include <Catalog.h>
31
32
33class BMessage;
34
35
36using BPrivate::HashMapCatalog;
37using BPrivate::AmigaCatalog;
38
39
40/* This add-on implements reading of Amiga catalog files. These are IFF files
41 * of type CTLG and used to localize Amiga applications. In most cases you
42 * should use the Haiku standard catalogs instead, unless:
43 * - You are porting an application from Amiga and want to use the same locale
44 * files
45 * - You want to use ID-based string lookup instead of string-to-string, for
46 * performance reasons.
47 */
48
49
50static const char *kCatFolder = "Catalogs/";
51static const char *kCatExtension = ".catalog";
52
53const char *AmigaCatalog::kCatMimeType
54 = "locale/x-vnd.Be.locale-catalog.amiga";
55
56static int16 kCatArchiveVersion = 1;
57 // version of the catalog archive structure, bump this if you change it!
58
59
60/*
61 * constructs a AmigaCatalog with given signature and language and reads
62 * the catalog from disk.
63 * InitCheck() will be B_OK if catalog could be loaded successfully, it will
64 * give an appropriate error-code otherwise.
65 */
Adrien Destuguesc102dfd2015-01-18 11:50:51 +010066AmigaCatalog::AmigaCatalog(const entry_ref& owner, const char *language,
Adrien Destugues374c6dc2015-01-11 16:37:56 +010067 uint32 fingerprint)
68 :
Adrien Destuguesc102dfd2015-01-18 11:50:51 +010069 HashMapCatalog("", language, fingerprint)
Adrien Destugues374c6dc2015-01-11 16:37:56 +010070{
Adrien Destuguesc102dfd2015-01-18 11:50:51 +010071 // This catalog uses the executable name to identify the catalog
72 // (not the MIME signature)
73 BEntry entry(&owner);
Adrien Destugues31e4a022020-06-01 13:06:54 +020074 char buffer[PATH_MAX];
Adrien Destuguesc102dfd2015-01-18 11:50:51 +010075 entry.GetName(buffer);
76 fSignature = buffer;
77
78 // This catalog uses the translated language name to identify the catalog
79 // (not the ISO language code)
80 BLanguage lang(language);
81 lang.GetNativeName(fLanguageName);
82
Adrien Destugues374c6dc2015-01-11 16:37:56 +010083 // give highest priority to catalog living in sub-folder of app's folder:
Adrien Destugues374c6dc2015-01-11 16:37:56 +010084 BString catalogName(kCatFolder);
85 catalogName << fLanguageName
86 << "/" << fSignature
87 << kCatExtension;
Adrien Destugues31e4a022020-06-01 13:06:54 +020088
89 image_info info;
90 int32 cookie = 0;
91 status_t r = get_next_image_info(B_CURRENT_TEAM, &cookie, &info);
92 BString dirName(dirname(info.name));
93 dirName << "/" << catalogName;
94
95 status_t status = ReadFromFile(dirName.String());
Adrien Destugues374c6dc2015-01-11 16:37:56 +010096
97 if (status != B_OK) {
98 // look in common-etc folder (/boot/home/config/etc):
99 BPath commonEtcPath;
100 find_directory(B_USER_ETC_DIRECTORY, &commonEtcPath);
101 if (commonEtcPath.InitCheck() == B_OK) {
Adrien Destugues31e4a022020-06-01 13:06:54 +0200102 dirName = BString(commonEtcPath.Path())
103 << "/" << catalogName;
104 status = ReadFromFile(dirName.String());
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100105 }
106 }
107
108 if (status != B_OK) {
109 // look in system-etc folder (/boot/beos/etc):
110 BPath systemEtcPath;
111 find_directory(B_SYSTEM_ETC_DIRECTORY, &systemEtcPath);
112 if (systemEtcPath.InitCheck() == B_OK) {
Adrien Destugues31e4a022020-06-01 13:06:54 +0200113 dirName = BString(systemEtcPath.Path())
114 << "/" << catalogName;
115 status = ReadFromFile(dirName.String());
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100116 }
117 }
118
119 fInitCheck = status;
120}
121
122
123/*
124 * constructs an empty AmigaCatalog with given sig and language.
125 * This is used for editing/testing purposes.
126 * InitCheck() will always be B_OK.
127 */
128AmigaCatalog::AmigaCatalog(const char *path, const char *signature,
129 const char *language)
130 :
131 HashMapCatalog(signature, language, 0),
132 fPath(path)
133{
134 fInitCheck = B_OK;
135}
136
137
138AmigaCatalog::~AmigaCatalog()
139{
140}
141
142
143status_t
144AmigaCatalog::ReadFromFile(const char *path)
145{
146 if (!path)
147 path = fPath.String();
148
149 BFile source(path, B_READ_ONLY);
150 if (source.InitCheck() != B_OK)
151 return source.InitCheck();
152
153 // Now read all the data from the file
154
155 int32 tmp;
156 source.Read(&tmp, sizeof(tmp)); // IFF header
157 if (ntohl(tmp) != 'FORM')
158 return B_BAD_DATA;
159
160 int32 dataSize;
161 source.Read(&dataSize, sizeof(dataSize)); // File size
162 dataSize = ntohl(dataSize);
163 source.Read(&tmp, sizeof(tmp)); // File type
164 if (ntohl(tmp) != 'CTLG')
165 return B_BAD_DATA;
166
167 dataSize -= 4; // Type is included in data size.
168
169 while(dataSize > 0) {
170 int32 chunkID, chunkSize;
171 source.Read(&chunkID, sizeof(chunkID));
172 source.Read(&chunkSize, sizeof(chunkSize));
173 chunkSize = ntohl(chunkSize);
174
175 // Round to word
176 if (chunkSize & 1) chunkSize++;
177
178 BStackOrHeapArray<char, 256> chunkData(chunkSize);
179 source.Read(chunkData, chunkSize);
180
181 chunkID = ntohl(chunkID);
182
183 switch(chunkID) {
184 case 'FVER': // Version
185 fSignature = chunkData;
186 break;
187 case 'LANG': // Language
188 fLanguageName = chunkData;
189 break;
190
191 case 'STRS': // Catalog strings
192 {
193 BMemoryIO strings(chunkData, chunkSize);
194 int32 strID, strLen;
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100195
196 while (strings.Position() < chunkSize) {
197 strings.Read(&strID, sizeof(strID));
198 strings.Read(&strLen, sizeof(strLen));
199 strID = ntohl(strID);
200 strLen = ntohl(strLen);
201 if (strLen & 3) {
202 strLen &= ~3;
203 strLen += 4;
204 }
Adrien Destuguese3b38e72017-11-13 21:56:57 +0100205 char strBase[strLen];
206 char* strVal = strBase;
207 strings.Read(strBase, strLen);
208
209 if (strBase[1] == 0)
210 {
211 // Skip the \0 marker for menu entries…
212 strLen -= 2;
213 strVal += 2;
214 }
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100215
Adrien Destuguesc102dfd2015-01-18 11:50:51 +0100216 char outVal[1024];
217 int32 outLen = 1024;
218 int32 cookie = 0;
219
220 convert_to_utf8(B_ISO1_CONVERSION, strVal, &strLen,
221 outVal, &outLen, &cookie);
222
Adrien Destuguese3b38e72017-11-13 21:56:57 +0100223 // If the UTF-8 version is shorter, it's likely that
224 // something went wrong. Keep the original string.
225 if (outLen > strLen)
226 SetString(strID, outVal);
227 else
228 SetString(strID, strVal);
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100229 }
230 break;
231 }
232
233 case 'CSET': // Unknown/unused
234 default:
235 break;
236 }
237
238 dataSize -= chunkSize + 8;
239 }
240
241 fPath = path;
242 fFingerprint = ComputeFingerprint();
243 return B_OK;
244}
245
246
247status_t
248AmigaCatalog::WriteToFile(const char *path)
249{
250 return B_NOT_SUPPORTED;
251}
252
253
254/*
255 * writes mimetype, language-name and signature of catalog into the
256 * catalog-file.
257 */
258void
259AmigaCatalog::UpdateAttributes(BFile& catalogFile)
260{
261 static const int bufSize = 256;
262 char buf[bufSize];
263 uint32 temp;
264 if (catalogFile.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, &buf, bufSize)
265 <= 0
266 || strcmp(kCatMimeType, buf) != 0) {
267 catalogFile.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0,
268 kCatMimeType, strlen(kCatMimeType)+1);
269 }
270 if (catalogFile.ReadAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
271 &buf, bufSize) <= 0
272 || fLanguageName != buf) {
273 catalogFile.WriteAttr(BLocaleRoster::kCatLangAttr, B_STRING_TYPE, 0,
274 fLanguageName.String(), fLanguageName.Length()+1);
275 }
276 if (catalogFile.ReadAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
277 &buf, bufSize) <= 0
278 || fSignature != buf) {
279 catalogFile.WriteAttr(BLocaleRoster::kCatSigAttr, B_STRING_TYPE, 0,
280 fSignature.String(), fSignature.Length()+1);
281 }
282 if (catalogFile.ReadAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
283 0, &temp, sizeof(uint32)) <= 0) {
284 catalogFile.WriteAttr(BLocaleRoster::kCatFingerprintAttr, B_UINT32_TYPE,
285 0, &fFingerprint, sizeof(uint32));
286 }
287}
288
289
290void
291AmigaCatalog::UpdateAttributes(const char* path)
292{
293 BEntry entry(path);
294 BFile node(&entry, B_READ_WRITE);
295 UpdateAttributes(node);
296}
297
298
299BCatalogData *
Adrien Destuguesc102dfd2015-01-18 11:50:51 +0100300AmigaCatalog::Instantiate(const entry_ref &owner, const char *language,
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100301 uint32 fingerprint)
302{
303 AmigaCatalog *catalog
Adrien Destuguesc102dfd2015-01-18 11:50:51 +0100304 = new(std::nothrow) AmigaCatalog(owner, language, fingerprint);
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100305 if (catalog && catalog->InitCheck() != B_OK) {
306 delete catalog;
307 return NULL;
308 }
309 return catalog;
310}
311
312
Adrien Destuguesc102dfd2015-01-18 11:50:51 +0100313// #pragma mark -
314
315
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100316extern "C" BCatalogData *
Adrien Destuguesc102dfd2015-01-18 11:50:51 +0100317instantiate_catalog(const entry_ref &owner, const char *language,
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100318 uint32 fingerprint)
319{
Adrien Destuguesc102dfd2015-01-18 11:50:51 +0100320 return AmigaCatalog::Instantiate(owner, language, fingerprint);
Adrien Destugues374c6dc2015-01-11 16:37:56 +0100321}
322
323
324extern "C" BCatalogData *
325create_catalog(const char *signature, const char *language)
326{
327 AmigaCatalog *catalog
328 = new(std::nothrow) AmigaCatalog("emptycat", signature, language);
329 return catalog;
330}
331
332
333extern "C" status_t
334get_available_languages(BMessage* availableLanguages,
335 const char* sigPattern = NULL, const char* langPattern = NULL,
336 int32 fingerprint = 0)
337{
338 // TODO
339 return B_ERROR;
340}
341
342
343uint8 gCatalogAddOnPriority = 80;