1 /*
2 * Copyright 2003-2007, Haiku. All Rights Reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * jonas.sundstrom@kirilla.com
8 */
9
10
11 #include <TranslationKit.h>
12 #include <Application.h>
13 #include <String.h>
14 #include <File.h>
15 #include <Path.h>
16 #include <Entry.h>
17 #include <Mime.h>
18 #include <NodeInfo.h>
19
20 #include <ctype.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <strings.h>
24
25
26 extern const char *__progname;
27 const char *gProgramName = __progname;
28 bool gVerbose = false;
29
30
31 class TypeList {
32 public:
33 void Add(uint32 type);
34 bool Remove(uint32 type);
35 bool FindType(uint32 type);
36
37 void SetTo(TypeList &types);
38 int32 Count();
39 uint32 TypeAt(int32 index);
40
41 private:
42 BList fList;
43 };
44
45 class RemovingFile : public BFile {
46 public:
47 RemovingFile(const char *path);
48 ~RemovingFile();
49
50 void Keep();
51
52 private:
53 BEntry fEntry;
54 };
55
56 class Translator {
57 public:
58 Translator(const char *inPath, const char *outPath, uint32 outFormat);
59 ~Translator();
60
61 status_t Translate();
62
63 private:
64 status_t Directly(BFile &input, translator_info &info, BFile &output);
65 status_t Indirectly(BFile &input, BFile &output);
66 status_t FindPath(const translation_format *info, BPositionIO &stream,
67 TypeList &typesSeen, TypeList &path, double &pathQuality);
68 status_t GetMimeTypeFromCode(uint32 type, char *mimeType);
69
70 private:
71 BTranslatorRoster *fRoster;
72 BPath fInputPath, fOutputPath;
73 uint32 fOutputFormat;
74 };
75
76 class TranslateApp : public BApplication {
77 public:
78 TranslateApp(void);
79 virtual ~TranslateApp();
80
81 virtual void ReadyToRun(void);
82 virtual void ArgvReceived(int32 argc, char **argv);
83
84 private:
85 void PrintUsage(void);
86 void ListTranslators(uint32 type);
87
88 uint32 GetTypeCodeForOutputMime(const char *mime);
89 uint32 GetTypeCodeFromString(const char *string);
90 status_t Translate(BFile &input, translator_info &translator, BFile &output, uint32 type);
91 status_t Translate(BFile &input, BFile &output, uint32 type);
92 status_t Translate(const char *inPath, const char *outPath, uint32 type);
93
94 bool fGotArguments;
95 BTranslatorRoster *fTranslatorRoster;
96 int32 fTranslatorCount;
97 translator_id *fTranslatorArray;
98 };
99
100
101 static void
print_tupel(const char * format,uint32 value)102 print_tupel(const char *format, uint32 value)
103 {
104 char tupel[5];
105
106 for (int32 i = 0; i < 4; i++) {
107 tupel[i] = (value >> (24 - i * 8)) & 0xff;
108 if (!isprint(tupel[i]))
109 tupel[i] = '.';
110 }
111 tupel[4] = '\0';
112
113 printf(format, tupel);
114 }
115
116
117 static void
print_translation_format(const translation_format & format)118 print_translation_format(const translation_format &format)
119 {
120 print_tupel("'%s' ", format.type);
121 print_tupel("(%s) ", format.group);
122
123 printf("%.1f %.1f %s ; %s\n", format.quality, format.capability,
124 format.MIME, format.name);
125 }
126
127
128 // #pragma mark -
129
130
131 void
Add(uint32 type)132 TypeList::Add(uint32 type)
133 {
134 fList.AddItem((void *)(addr_t)type, 0);
135 }
136
137
138 bool
Remove(uint32 type)139 TypeList::Remove(uint32 type)
140 {
141 return fList.RemoveItem((void *)(addr_t)type);
142 }
143
144
145 bool
FindType(uint32 type)146 TypeList::FindType(uint32 type)
147 {
148 return fList.IndexOf((void *)(addr_t)type) >= 0;
149 }
150
151
152 void
SetTo(TypeList & types)153 TypeList::SetTo(TypeList &types)
154 {
155 fList.MakeEmpty();
156
157 for (int32 i = 0; i < types.Count(); i++)
158 fList.AddItem((void *)(addr_t)types.TypeAt(i));
159 }
160
161
162 int32
Count()163 TypeList::Count()
164 {
165 return fList.CountItems();
166 }
167
168
169 uint32
TypeAt(int32 index)170 TypeList::TypeAt(int32 index)
171 {
172 return (uint32)(addr_t)fList.ItemAt(index);
173 }
174
175
176 // #pragma mark -
177
178
RemovingFile(const char * path)179 RemovingFile::RemovingFile(const char *path)
180 : BFile(path, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE),
181 fEntry(path)
182 {
183 }
184
185
~RemovingFile()186 RemovingFile::~RemovingFile()
187 {
188 fEntry.Remove();
189 }
190
191
192 void
Keep()193 RemovingFile::Keep()
194 {
195 fEntry.Unset();
196 }
197
198
199 // #pragma mark -
200
201
Translator(const char * inPath,const char * outPath,uint32 outputFormat)202 Translator::Translator(const char *inPath, const char *outPath, uint32 outputFormat)
203 :
204 fRoster(BTranslatorRoster::Default()),
205 fInputPath(inPath),
206 fOutputPath(outPath),
207 fOutputFormat(outputFormat)
208 {
209 }
210
211
~Translator()212 Translator::~Translator()
213 {
214 }
215
216
217 status_t
Translate()218 Translator::Translate()
219 {
220 // input file
221 BFile input;
222 status_t status = input.SetTo(fInputPath.Path(), B_READ_ONLY);
223 if (status != B_OK) {
224 fprintf(stderr, "%s: could not open \"%s\": %s\n",
225 gProgramName, fInputPath.Path(), strerror(status));
226 return status;
227 }
228
229 // find a direct translator
230 bool direct = true;
231 translator_info translator;
232 status = fRoster->Identify(&input, NULL, &translator, 0, NULL, fOutputFormat);
233 if (status < B_OK) {
234 // no direct translator found - let's try with something else
235 status = fRoster->Identify(&input, NULL, &translator);
236 if (status < B_OK) {
237 fprintf(stderr, "%s: identifying \"%s\" failed: %s\n",
238 gProgramName, fInputPath.Path(), strerror(status));
239 return status;
240 }
241
242 direct = false;
243 }
244
245 // output file
246 RemovingFile output(fOutputPath.Path());
247 if ((status = output.InitCheck()) != B_OK) {
248 fprintf(stderr, "%s: Could not create \"%s\": %s\n",
249 gProgramName, fOutputPath.Path(), strerror(status));
250 return status;
251 }
252
253 if (direct)
254 status = Directly(input, translator, output);
255 else
256 status = Indirectly(input, output);
257
258 if (status == B_OK) {
259 output.Keep();
260
261 // add filetype attribute
262 update_mime_info(fOutputPath.Path(), false, true, false);
263
264 char mimeType[B_ATTR_NAME_LENGTH];
265 BNode node(fOutputPath.Path());
266 BNodeInfo info(&node);
267 if (info.GetType(mimeType) != B_OK || !strcasecmp(mimeType, B_FILE_MIME_TYPE)) {
268 // the Registrar couldn't find a type for this file
269 // so let's use the information we have from the
270 // translator
271 if (GetMimeTypeFromCode(fOutputFormat, mimeType) == B_OK)
272 info.SetType(mimeType);
273 }
274 } else {
275 fprintf(stderr, "%s: translating failed: %s\n",
276 gProgramName, strerror(status));
277 }
278
279 return status;
280 }
281
282
283 /** Converts the input file to the output file using the
284 * specified translator.
285 */
286
287 status_t
Directly(BFile & input,translator_info & info,BFile & output)288 Translator::Directly(BFile &input, translator_info &info, BFile &output)
289 {
290 if (gVerbose)
291 printf("Direct translation from \"%s\"\n", info.name);
292
293 return fRoster->Translate(&input, &info, NULL, &output, fOutputFormat);
294 }
295
296
297 /** Converts the input file to the output file by computing the best
298 * quality path between the input type and the output type, and then
299 * applies as many translators as needed.
300 */
301
302 status_t
Indirectly(BFile & input,BFile & output)303 Translator::Indirectly(BFile &input, BFile &output)
304 {
305 TypeList translatorPath;
306 TypeList handledTypes;
307 double quality;
308
309 if (FindPath(NULL, input, handledTypes, translatorPath, quality) != B_OK)
310 return B_NO_TRANSLATOR;
311
312 // The initial input stream is the input file which gets translated into
313 // the first buffer. After that, the two buffers fill each other, until
314 // the end is reached and the output file is the translation target
315
316 BMallocIO buffer[2];
317 BPositionIO *inputStream = &input;
318 BPositionIO *outputStream = &buffer[0];
319
320 for (int32 i = 0; i < translatorPath.Count(); i++) {
321 uint32 type = translatorPath.TypeAt(i);
322 if (type == fOutputFormat)
323 outputStream = &output;
324 else
325 outputStream->SetSize(0);
326
327 inputStream->Seek(0, SEEK_SET);
328 // rewind the input stream
329
330 status_t status = fRoster->Translate(inputStream, NULL, NULL, outputStream, type);
331 if (status != B_OK)
332 return status;
333
334 // switch buffers
335 inputStream = &buffer[i % 2];
336 outputStream = &buffer[(i + 1) % 2];
337
338 if (gVerbose)
339 print_tupel(" '%s'\n", type);
340 }
341
342 return B_OK;
343 }
344
345
346 status_t
FindPath(const translation_format * format,BPositionIO & stream,TypeList & typesSeen,TypeList & path,double & pathQuality)347 Translator::FindPath(const translation_format *format, BPositionIO &stream,
348 TypeList &typesSeen, TypeList &path, double &pathQuality)
349 {
350 translator_info *infos = NULL;
351 translator_id *ids = NULL;
352 int32 count;
353 uint32 inFormat = 0;
354 uint32 group = 0;
355 status_t status;
356
357 // Get a list of capable translators (or all of them)
358
359 if (format == NULL) {
360 status = fRoster->GetTranslators(&stream, NULL, &infos, &count);
361 if (status == B_OK && count > 0) {
362 inFormat = infos[0].type;
363 group = infos[0].group;
364
365 if (gVerbose) {
366 puts("Indirect translation:");
367 print_tupel(" '%s', ", inFormat);
368 printf("%s\n", infos[0].name);
369 }
370 }
371 } else {
372 status = fRoster->GetAllTranslators(&ids, &count);
373 inFormat = format->type;
374 group = format->group;
375 }
376 if (status != B_OK || count == 0) {
377 delete[] infos;
378 delete[] ids;
379 return status;
380 }
381
382 // build the best path to get from here to fOutputFormat recursively
383 // (via depth search, best quality/capability wins)
384
385 TypeList bestPath;
386 double bestQuality = -1;
387 status = B_NO_TRANSLATOR;
388
389 for (int32 i = 0; i < count; i++) {
390 const translation_format *formats;
391 int32 formatCount;
392 bool matches = false;
393 int32 id = ids ? ids[i] : infos[i].translator;
394 if (fRoster->GetInputFormats(id, &formats, &formatCount) != B_OK)
395 continue;
396
397 // see if this translator is good enough for us
398 for (int32 j = 0; j < formatCount; j++) {
399 if (formats[j].type == inFormat) {
400 matches = true;
401 break;
402 }
403 }
404
405 if (!matches)
406 continue;
407
408 // search output formats
409
410 if (fRoster->GetOutputFormats(id, &formats, &formatCount) != B_OK)
411 continue;
412
413 typesSeen.Add(inFormat);
414
415 for (int32 j = 0; j < formatCount; j++) {
416 if (formats[j].group != group || typesSeen.FindType(formats[j].type))
417 continue;
418
419 double formatQuality = formats[j].quality * formats[j].capability;
420
421 if (formats[j].type == fOutputFormat) {
422 // we've found our target type, so we can stop the path here
423 bestPath.Add(formats[j].type);
424 bestQuality = formatQuality;
425 status = B_OK;
426 continue;
427 }
428
429 TypeList path;
430 double quality;
431 if (FindPath(&formats[j], stream, typesSeen, path, quality) == B_OK) {
432 if (bestQuality < quality * formatQuality) {
433 bestQuality = quality * formatQuality;
434 bestPath.SetTo(path);
435 bestPath.Add(formats[j].type);
436 status = B_OK;
437 }
438 }
439 }
440
441 typesSeen.Remove(inFormat);
442 }
443
444 if (status == B_OK) {
445 pathQuality = bestQuality;
446 path.SetTo(bestPath);
447 }
448 delete[] infos;
449 delete[] ids;
450 return status;
451 }
452
453
454 status_t
GetMimeTypeFromCode(uint32 type,char * mimeType)455 Translator::GetMimeTypeFromCode(uint32 type, char *mimeType)
456 {
457 translator_id *ids = NULL;
458 int32 count;
459 status_t status = fRoster->GetAllTranslators(&ids, &count);
460 if (status != B_OK)
461 return status;
462
463 status = B_NO_TRANSLATOR;
464
465 for (int32 i = 0; i < count; i++) {
466 const translation_format *format = NULL;
467 int32 formatCount = 0;
468 fRoster->GetOutputFormats(ids[i], &format, &formatCount);
469
470 for (int32 j = 0; j < formatCount; j++) {
471 if (type == format[j].type) {
472 strcpy(mimeType, format[j].MIME);
473 status = B_OK;
474 break;
475 }
476 }
477 }
478
479 delete[] ids;
480 return status;
481 }
482
483
484 // #pragma mark -
485
486
TranslateApp(void)487 TranslateApp::TranslateApp(void)
488 : BApplication("application/x-vnd.haiku-translate"),
489 fGotArguments(false),
490 fTranslatorRoster(BTranslatorRoster::Default()),
491 fTranslatorCount(0),
492 fTranslatorArray(NULL)
493 {
494 fTranslatorRoster->GetAllTranslators(&fTranslatorArray, &fTranslatorCount);
495 }
496
497
~TranslateApp()498 TranslateApp::~TranslateApp()
499 {
500 delete[] fTranslatorArray;
501 }
502
503
504 void
ArgvReceived(int32 argc,char ** argv)505 TranslateApp::ArgvReceived(int32 argc, char **argv)
506 {
507 if (argc < 2
508 || !strcmp(argv[1], "--help"))
509 return;
510
511 if (!strcmp(argv[1], "--list")) {
512 fGotArguments = true;
513
514 uint32 type = B_TRANSLATOR_ANY_TYPE;
515 if (argc > 2)
516 type = GetTypeCodeFromString(argv[2]);
517
518 ListTranslators(type);
519 return;
520 }
521
522 if (!strcmp(argv[1], "--verbose")) {
523 fGotArguments = true;
524 argc--;
525 argv++;
526 gVerbose = true;
527 }
528
529 if (argc != 4)
530 return;
531
532 fGotArguments = true;
533
534 // get typecode of output format
535 uint32 outputFormat = 0;
536 BMimeType mime(argv[3]);
537
538 if (mime.IsValid() && !mime.IsSupertypeOnly()) {
539 // MIME-string
540 outputFormat = GetTypeCodeForOutputMime(argv[3]);
541 } else
542 outputFormat = GetTypeCodeFromString(argv[3]);
543
544 if (outputFormat == 0) {
545 fprintf(stderr, "%s: bad format: %s\nformat is 4-byte type code or full MIME type\n",
546 gProgramName, argv[3]);
547 exit(-1);
548 }
549
550 Translator translator(argv[1], argv[2], outputFormat);
551 status_t status = translator.Translate();
552 if (status < B_OK)
553 exit(-1);
554 }
555
556
557 void
ReadyToRun(void)558 TranslateApp::ReadyToRun(void)
559 {
560 if (fGotArguments == false)
561 PrintUsage();
562
563 PostMessage(B_QUIT_REQUESTED);
564 }
565
566
567 void
PrintUsage(void)568 TranslateApp::PrintUsage(void)
569 {
570 printf("usage: %s { --list [type] | input output format }\n"
571 "\t\"format\" can expressed as 4-byte type code (ie. 'TEXT') or as MIME type.\n",
572 gProgramName);
573 }
574
575
576 void
ListTranslators(uint32 type)577 TranslateApp::ListTranslators(uint32 type)
578 {
579 for (int32 i = 0; i < fTranslatorCount; i++) {
580 const char *name = NULL;
581 const char *info = NULL;
582 int32 version = 0;
583 if (fTranslatorRoster->GetTranslatorInfo(fTranslatorArray[i], &name, &info, &version) != B_OK)
584 continue;
585
586 const translation_format *inputFormats = NULL;
587 const translation_format *outputFormats = NULL;
588 int32 inCount = 0, outCount = 0;
589 fTranslatorRoster->GetInputFormats(fTranslatorArray[i], &inputFormats, &inCount);
590 fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &outputFormats, &outCount);
591
592 // test if the translator has formats of the specified type
593
594 if (type != B_TRANSLATOR_ANY_TYPE) {
595 bool matches = false;
596
597 for (int32 j = 0; j < inCount; j++) {
598 if (inputFormats[j].group == type || inputFormats[j].type == type) {
599 matches = true;
600 break;
601 }
602 }
603
604 for (int32 j = 0; j < outCount; j++) {
605 if (outputFormats[j].group == type || outputFormats[j].type == type) {
606 matches = true;
607 break;
608 }
609 }
610
611 if (!matches)
612 continue;
613 }
614
615 printf("name: %s\ninfo: %s\nversion: %" B_PRId32 ".%" B_PRId32 ".%"
616 B_PRId32 "\n", name, info,
617 B_TRANSLATION_MAJOR_VERSION(version),
618 B_TRANSLATION_MINOR_VERSION(version),
619 B_TRANSLATION_REVISION_VERSION(version));
620
621 for (int32 j = 0; j < inCount; j++) {
622 printf(" input:\t");
623 print_translation_format(inputFormats[j]);
624 }
625
626 for (int32 j = 0; j < outCount; j++) {
627 printf(" output:\t");
628 print_translation_format(outputFormats[j]);
629 }
630
631 printf("\n");
632 }
633 }
634
635
636 uint32
GetTypeCodeForOutputMime(const char * mime)637 TranslateApp::GetTypeCodeForOutputMime(const char *mime)
638 {
639 for (int32 i = 0; i < fTranslatorCount; i++) {
640 const translation_format *format = NULL;
641 int32 count = 0;
642 fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &format, &count);
643
644 for (int32 j = 0; j < count; j++) {
645 if (!strcmp(mime, format[j].MIME))
646 return format[j].type;
647 }
648 }
649
650 return 0;
651 }
652
653
654 uint32
GetTypeCodeFromString(const char * string)655 TranslateApp::GetTypeCodeFromString(const char *string)
656 {
657 size_t length = strlen(string);
658 if (length > 4)
659 return 0;
660
661 uint8 code[4] = {' ', ' ', ' ', ' '};
662
663 for (uint32 i = 0; i < length; i++)
664 code[i] = (uint8)string[i];
665
666 return B_HOST_TO_BENDIAN_INT32(*(uint32 *)code);
667 }
668
669
670 // #pragma mark -
671
672
673 int
main()674 main()
675 {
676 new TranslateApp();
677 be_app->Run();
678
679 return B_OK;
680 }
681
682