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
26extern const char *__progname;
27const char *gProgramName = __progname;
28bool gVerbose = false;
29
30
31class 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
45class RemovingFile : public BFile {
46	public:
47		RemovingFile(const char *path);
48		~RemovingFile();
49
50		void Keep();
51
52	private:
53		BEntry	fEntry;
54};
55
56class 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
76class 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
101static void
102print_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
117static void
118print_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
131void
132TypeList::Add(uint32 type)
133{
134	fList.AddItem((void *)(addr_t)type, 0);
135}
136
137
138bool
139TypeList::Remove(uint32 type)
140{
141	return fList.RemoveItem((void *)(addr_t)type);
142}
143
144
145bool
146TypeList::FindType(uint32 type)
147{
148	return fList.IndexOf((void *)(addr_t)type) >= 0;
149}
150
151
152void
153TypeList::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
162int32
163TypeList::Count()
164{
165	return fList.CountItems();
166}
167
168
169uint32
170TypeList::TypeAt(int32 index)
171{
172	return (uint32)(addr_t)fList.ItemAt(index);
173}
174
175
176//	#pragma mark -
177
178
179RemovingFile::RemovingFile(const char *path)
180	: BFile(path, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE),
181	fEntry(path)
182{
183}
184
185
186RemovingFile::~RemovingFile()
187{
188	fEntry.Remove();
189}
190
191
192void
193RemovingFile::Keep()
194{
195	fEntry.Unset();
196}
197
198
199//	#pragma mark -
200
201
202Translator::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
212Translator::~Translator()
213{
214}
215
216
217status_t
218Translator::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
287status_t
288Translator::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
302status_t
303Translator::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
346status_t
347Translator::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
454status_t
455Translator::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
487TranslateApp::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
498TranslateApp::~TranslateApp()
499{
500	delete[] fTranslatorArray;
501}
502
503
504void
505TranslateApp::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
557void
558TranslateApp::ReadyToRun(void)
559{
560	if (fGotArguments == false)
561		PrintUsage();
562
563	PostMessage(B_QUIT_REQUESTED);
564}
565
566
567void
568TranslateApp::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
576void
577TranslateApp::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
636uint32
637TranslateApp::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
654uint32
655TranslateApp::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
673int
674main()
675{
676	new TranslateApp();
677	be_app->Run();
678
679	return B_OK;
680}
681
682