Almost complete rewrite of the translate command. Fixed broken argument

parsing and type code conversion.
Some additions/changes to the original:
- now prints the version information in the new versioning scheme (base 16)
- added optional --verbose parameter that gives information about the
  translations needed to get the output format.
- "--list" can be constraint with a type - only translators that support
  this type are listed then.
- also accepts type codes like "RTF" and automatically adds spaces in
  this case to create the 'RTF ' type.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@11243 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2005-02-04 04:04:52 +00:00
parent 998521f78d
commit 9324698b19
+570 -200
View File
@@ -1,266 +1,636 @@
// translate - for OpenBeOS
//
// authors - in order of contribution
// [email protected]
//
// bugs & issues
// -- type code doesn't work, needs MIME format in argv[3]
//
// libs: be, root, translation
//
/*
* Copyright 2003-2005, Haiku. All Rights Reserved.
* Distributed under the terms of the MIT license.
*
* Authors:
* Axel Dörfler, [email protected]
* [email protected]
*/
#include <stdio.h>
#include <string.h>
#include <TranslationKit.h>
#include <Application.h>
#include <String.h>
#include <File.h>
#include <Path.h>
#include <Entry.h>
#include <Mime.h>
class TranslateApp : public BApplication
{
public:
TranslateApp (void);
virtual void ReadyToRun (void);
virtual void ArgvReceived (int32 argc, char **argv);
#include <stdio.h>
#include <string.h>
#include <ctype.h>
extern const char *__progname;
const char *gProgramName = __progname;
bool gVerbose = false;
class TypeList {
public:
void Add(uint32 type);
bool Remove(uint32 type);
bool FindType(uint32 type);
void SetTo(TypeList &types);
int32 Count();
uint32 TypeAt(int32 index);
private:
void PrintUsage (void);
void ListTranslators (void);
uint32 GetTypecodeForOutputMime (const char * a_mime);
void RemoveOutputFile (char * path);
bool m_got_arguments;
BTranslatorRoster * translator_roster;
int32 translators_total;
translator_id * translator_array;
BList fList;
};
int main()
class RemovingFile : public BFile {
public:
RemovingFile(const char *path);
~RemovingFile();
void Keep();
private:
BEntry fEntry;
};
class Translator {
public:
Translator(const char *inPath, const char *outPath, uint32 outFormat);
~Translator();
status_t Translate();
private:
status_t Directly(BFile &input, translator_info &info, BFile &output);
status_t Indirectly(BFile &input, BFile &output);
status_t FindPath(const translation_format *info, BPositionIO &stream,
TypeList &typesSeen, TypeList &path, double &pathQuality);
private:
BTranslatorRoster *fRoster;
BPath fInputPath, fOutputPath;
uint32 fOutputFormat;
};
class TranslateApp : public BApplication {
public:
TranslateApp(void);
virtual ~TranslateApp();
virtual void ReadyToRun(void);
virtual void ArgvReceived(int32 argc, char **argv);
private:
void PrintUsage(void);
void ListTranslators(uint32 type);
uint32 GetTypeCodeForOutputMime(const char *mime);
uint32 GetTypeCodeFromString(const char *string);
status_t Translate(BFile &input, translator_info &translator, BFile &output, uint32 type);
status_t Translate(BFile &input, BFile &output, uint32 type);
status_t Translate(const char *inPath, const char *outPath, uint32 type);
bool fGotArguments;
BTranslatorRoster *fTranslatorRoster;
int32 fTranslatorCount;
translator_id *fTranslatorArray;
};
static void
print_tupel(const char *format, uint32 value)
{
new TranslateApp ();
be_app->Run();
char tupel[5];
for (int32 i = 0; i < 4; i++) {
tupel[i] = (value >> (24 - i * 8)) & 0xff;
if (!isprint(tupel[i]))
tupel[i] = '.';
}
tupel[4] = '\0';
printf(format, tupel);
}
static void
print_translation_format(const translation_format &format)
{
print_tupel("'%s' ", format.type);
print_tupel("(%s) ", format.group);
printf("%.1f %.1f %s ; %s\n", format.quality, format.capability,
format.MIME, format.name);
}
// #pragma mark -
void
TypeList::Add(uint32 type)
{
fList.AddItem((void *)type, 0);
}
bool
TypeList::Remove(uint32 type)
{
return fList.RemoveItem((void *)type);
}
bool
TypeList::FindType(uint32 type)
{
return fList.IndexOf((void *)type) >= 0;
}
void
TypeList::SetTo(TypeList &types)
{
fList.MakeEmpty();
for (int32 i = 0; i < types.Count(); i++)
fList.AddItem((void *)types.TypeAt(i));
}
int32
TypeList::Count()
{
return fList.CountItems();
}
uint32
TypeList::TypeAt(int32 index)
{
return (uint32)fList.ItemAt(index);
}
// #pragma mark -
RemovingFile::RemovingFile(const char *path)
: BFile(path, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE),
fEntry(path)
{
}
RemovingFile::~RemovingFile()
{
fEntry.Remove();
}
void
RemovingFile::Keep()
{
fEntry.Unset();
}
// #pragma mark -
Translator::Translator(const char *inPath, const char *outPath, uint32 outputFormat)
:
fRoster(BTranslatorRoster::Default()),
fInputPath(inPath),
fOutputPath(outPath),
fOutputFormat(outputFormat)
{
}
Translator::~Translator()
{
}
status_t
Translator::Translate()
{
// input file
BFile input;
status_t status = input.SetTo(fInputPath.Path(), B_READ_ONLY);
if (status != B_OK) {
fprintf(stderr, "%s: could not open \"%s\": %s\n",
gProgramName, fInputPath.Path(), strerror(status));
return status;
}
// find a direct translator
bool direct = true;
translator_info translator;
status = fRoster->Identify(&input, NULL, &translator, 0, NULL, fOutputFormat);
if (status < B_OK) {
// no direct translator found - let's try with something else
status = fRoster->Identify(&input, NULL, &translator);
if (status < B_OK) {
fprintf(stderr, "%s: identifying \"%s\" failed: %s\n",
gProgramName, fInputPath.Path(), strerror(status));
return status;
}
direct = false;
}
// output file
RemovingFile output(fOutputPath.Path());
if ((status = output.InitCheck()) != B_OK) {
fprintf(stderr, "%s: Could not create \"%s\": %s\n",
gProgramName, fOutputPath.Path(), strerror(status));
return status;
}
if (direct)
status = Directly(input, translator, output);
else
status = Indirectly(input, output);
if (status == B_OK) {
output.Keep();
// add filetype attribute
update_mime_info(fOutputPath.Path(), false, true, false);
} else {
fprintf(stderr, "%s: translating failed: %s\n",
gProgramName, strerror(status));
}
return status;
}
/** Converts the input file to the output file using the
* specified translator.
*/
status_t
Translator::Directly(BFile &input, translator_info &info, BFile &output)
{
if (gVerbose)
printf("Direct translation from \"%s\"\n", info.name);
return fRoster->Translate(&input, &info, NULL, &output, fOutputFormat);
}
/** Converts the input file to the output file by computing the best
* quality path between the input type and the output type, and then
* applies as many translators as needed.
*/
status_t
Translator::Indirectly(BFile &input, BFile &output)
{
TypeList translatorPath;
TypeList handledTypes;
double quality;
if (FindPath(NULL, input, handledTypes, translatorPath, quality) != B_OK)
return B_NO_TRANSLATOR;
// The initial input stream is the input file which gets translated into
// the first buffer. After that, the two buffers fill each other, until
// the end is reached and the output file is the translation target
BMallocIO buffer[2];
BPositionIO *inputStream = &input;
BPositionIO *outputStream = &buffer[0];
for (int32 i = 0; i < translatorPath.Count(); i++) {
uint32 type = translatorPath.TypeAt(i);
if (type == fOutputFormat)
outputStream = &output;
else
outputStream->SetSize(0);
inputStream->Seek(0, SEEK_SET);
// rewind the input stream
status_t status = fRoster->Translate(inputStream, NULL, NULL, outputStream, type);
if (status != B_OK)
return status;
// switch buffers
inputStream = &buffer[i % 2];
outputStream = &buffer[(i + 1) % 2];
if (gVerbose)
print_tupel(" '%s'\n", type);
}
return B_OK;
}
TranslateApp::TranslateApp (void) :
BApplication ("application/x-vnd.obos-translate"),
m_got_arguments (false),
translator_roster (BTranslatorRoster::Default()),
translators_total (0),
translator_array (NULL)
status_t
Translator::FindPath(const translation_format *format, BPositionIO &stream,
TypeList &typesSeen, TypeList &path, double &pathQuality)
{
translator_roster->GetAllTranslators (& translator_array, & translators_total);
translator_info *infos = NULL;
translator_id *ids = NULL;
int32 count;
uint32 inFormat = 0;
uint32 group = 0;
status_t status;
// Get a list of capable translators (or all of them)
if (format == NULL) {
status = fRoster->GetTranslators(&stream, NULL, &infos, &count);
if (status == B_OK && count > 0) {
inFormat = infos[0].type;
group = infos[0].group;
if (gVerbose) {
puts("Indirect translation:");
print_tupel(" '%s', ", inFormat);
printf("%s\n", infos[0].name);
}
}
} else {
status = fRoster->GetAllTranslators(&ids, &count);
inFormat = format->type;
group = format->group;
}
if (status != B_OK || count == 0) {
delete[] infos;
delete[] ids;
return status;
}
// build the best path to get from here to fOutputFormat recursively
// (via depth search, best quality/capability wins)
TypeList bestPath;
double bestQuality = -1;
status = B_NO_TRANSLATOR;
for (int32 i = 0; i < count; i++) {
const translation_format *formats;
int32 formatCount;
bool matches = false;
int32 id = ids ? ids[i] : infos[i].translator;
if (fRoster->GetInputFormats(id, &formats, &formatCount) != B_OK)
continue;
// see if this translator is good enough for us
for (int32 j = 0; j < formatCount; j++) {
if (formats[j].type == inFormat) {
matches = true;
break;
}
}
if (!matches)
continue;
// search output formats
if (fRoster->GetOutputFormats(id, &formats, &formatCount) != B_OK)
continue;
typesSeen.Add(inFormat);
for (int32 j = 0; j < formatCount; j++) {
if (formats[j].group != group || typesSeen.FindType(formats[j].type))
continue;
double formatQuality = formats[j].quality * formats[j].capability;
if (formats[j].type == fOutputFormat) {
// we've found our target type, so we can stop the path here
bestPath.Add(formats[j].type);
bestQuality = formatQuality;
status = B_OK;
continue;
}
TypeList path;
double quality;
if (FindPath(&formats[j], stream, typesSeen, path, quality) == B_OK) {
if (bestQuality < quality * formatQuality) {
bestQuality = quality * formatQuality;
bestPath.SetTo(path);
bestPath.Add(formats[j].type);
status = B_OK;
}
}
}
typesSeen.Remove(inFormat);
}
if (status == B_OK) {
pathQuality = bestQuality;
path.SetTo(bestPath);
}
delete[] infos;
delete[] ids;
return status;
}
void TranslateApp::ArgvReceived (int32 argc, char ** argv)
// #pragma mark -
TranslateApp::TranslateApp(void)
: BApplication("application/x-vnd.haiku-translate"),
fGotArguments(false),
fTranslatorRoster(BTranslatorRoster::Default()),
fTranslatorCount(0),
fTranslatorArray(NULL)
{
status_t status = B_OK;
fTranslatorRoster->GetAllTranslators(&fTranslatorArray, &fTranslatorCount);
}
BString first_arg = argv[1];
first_arg.ToLower();
// --help
if (first_arg.IFindFirst("--help") == 0)
{
m_got_arguments = true;
PrintUsage();
TranslateApp::~TranslateApp()
{
delete[] fTranslatorArray;
}
void
TranslateApp::ArgvReceived(int32 argc, char **argv)
{
if (argc < 2
|| !strcmp(argv[1], "--help"))
return;
if (!strcmp(argv[1], "--list")) {
fGotArguments = true;
uint32 type = B_TRANSLATOR_ANY_TYPE;
if (argc > 2)
type = GetTypeCodeFromString(argv[2]);
ListTranslators(type);
return;
}
// --list
if (first_arg.IFindFirst("--list") == 0)
{
m_got_arguments = true;
ListTranslators();
return;
if (!strcmp(argv[1], "--verbose")) {
fGotArguments = true;
argc--;
argv++;
gVerbose = true;
}
if (argc != 4)
return;
m_got_arguments = true;
// input file
BFile in_file (argv[1], B_READ_ONLY);
status = in_file.InitCheck();
if (status != B_OK)
{
printf("translate: %s : %s\n", argv[1], strerror(status));
exit (-1);
}
// find input translator
translator_info best_suited_translator;
status = translator_roster->Identify(& in_file, NULL, & best_suited_translator);
fGotArguments = true;
// get typecode of output format
uint32 out_format = 0;
BMimeType mime (argv[3]);
uint32 outputFormat = 0;
BMimeType mime(argv[3]);
if (mime.IsValid() && (! mime.IsSupertypeOnly())) // MIME-string
{
out_format = GetTypecodeForOutputMime(argv[3]);
}
else // 4-byte code
{
if (strlen(argv[3]) == 4)
out_format = (uint32) argv[3];
else
out_format = 0;
}
if (out_format == 0)
{
printf("bad format: %s\nformat is 4-byte type code or full MIME type\n", argv[3]);
exit (-1);
}
// output file
BFile out_file (argv[2], B_READ_WRITE | B_CREATE_FILE | B_FAIL_IF_EXISTS);
status = out_file.InitCheck();
if (mime.IsValid() && !mime.IsSupertypeOnly()) {
// MIME-string
outputFormat = GetTypeCodeForOutputMime(argv[3]);
} else
outputFormat = GetTypeCodeFromString(argv[3]);
if (status != B_OK)
{
printf("translate: %s : %s\n", argv[2], strerror(status));
exit (-1);
}
// translate
BMallocIO * malloc_io = new BMallocIO;
status = translator_roster->Translate(& in_file, NULL, NULL, malloc_io, best_suited_translator.group);
if (status != B_OK)
{
printf("translate: translation of %s to base format: %s\n", argv[1], strerror(status));
delete malloc_io;
RemoveOutputFile(argv[2]);
exit (-1);
if (outputFormat == 0) {
fprintf(stderr, "%s: bad format: %s\nformat is 4-byte type code or full MIME type\n",
gProgramName, argv[3]);
exit(-1);
}
in_file.Unset();
status = translator_roster->Translate(malloc_io, NULL, NULL, & out_file, out_format);
if (status != B_OK)
{
printf("translate: translation from base format to %s: %s\n", argv[3], strerror(status));
delete malloc_io;
RemoveOutputFile(argv[2]);
exit (-1);
}
delete malloc_io;
out_file.Unset();
// add filetype attribute
update_mime_info(argv[2], 0, 1, 0);
Translator translator(argv[1], argv[2], outputFormat);
status_t status = translator.Translate();
if (status < B_OK)
exit(-1);
}
void TranslateApp::ReadyToRun (void)
void
TranslateApp::ReadyToRun(void)
{
if (m_got_arguments == false)
if (fGotArguments == false)
PrintUsage();
PostMessage(B_QUIT_REQUESTED);
}
void TranslateApp::PrintUsage (void)
void
TranslateApp::PrintUsage(void)
{
printf ("usage: translate { --list | input output format }\n");
printf("usage: %s { --list [type] | input output format }\n"
"\t\"format\" can expressed as 4-byte type code (ie. 'TEXT') or as MIME type.\n",
gProgramName);
}
void TranslateApp::ListTranslators (void)
void
TranslateApp::ListTranslators(uint32 type)
{
const char * translator_name = NULL;
const char * translator_info = NULL;
int32 translator_version = 0;
for (int32 i = 0; i < translators_total; i++)
{
translator_roster->GetTranslatorInfo(translator_array[i], & translator_name, & translator_info, & translator_version);
printf("name: %s\ninfo: %s\nversion: %.2f\n", translator_name, translator_info, translator_version/100.);
for (int32 i = 0; i < fTranslatorCount; i++) {
const char *name = NULL;
const char *info = NULL;
int32 version = 0;
if (fTranslatorRoster->GetTranslatorInfo(fTranslatorArray[i], &name, &info, &version) != B_OK)
continue;
const translation_format * format = NULL;
int32 format_num = 0;
translator_roster->GetInputFormats(translator_array[i], & format, & format_num);
const translation_format *inputFormats = NULL;
const translation_format *outputFormats = NULL;
int32 inCount = 0, outCount = 0;
fTranslatorRoster->GetInputFormats(fTranslatorArray[i], &inputFormats, &inCount);
fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &outputFormats, &outCount);
for (int32 ii = 0; ii < format_num; ii++)
{
printf("input:\t");
// test if the translator has formats of the specified type
printf("'%c", (short) (format[ii].type >> 24));
printf("%c", (short) ((format[ii].type << 8) >> 24));
printf("%c", (short) ((format[ii].type << 16) >> 24));
printf("%c' ", (short) ((format[ii].type << 24) >> 24));
if (type != B_TRANSLATOR_ANY_TYPE) {
bool matches = false;
printf("(%c", (short) (format[ii].group >> 24));
printf("%c", (short) ((format[ii].group << 8) >> 24));
printf("%c", (short) ((format[ii].group << 16) >> 24));
printf("%c) ", (short) ((format[ii].group << 24) >> 24));
printf("%.1f ", format[ii].quality);
printf("%.1f ", format[ii].capability);
printf("%s ; ", format[ii].MIME);
printf("%s\n", format[ii].name);
for (int32 j = 0; j < inCount; j++) {
if (inputFormats[j].group == type || inputFormats[j].type == type) {
matches = true;
break;
}
}
for (int32 j = 0; j < outCount; j++) {
if (outputFormats[j].group == type || outputFormats[j].type == type) {
matches = true;
break;
}
}
if (!matches)
continue;
}
translator_roster->GetOutputFormats(translator_array[i], & format, & format_num);
for (int32 ii = 0; ii < format_num; ii++)
{
printf("output:\t");
printf("'%c", (short) (format[ii].type >> 24));
printf("%c", (short) ((format[ii].type << 8) >> 24));
printf("%c", (short) ((format[ii].type << 16) >> 24));
printf("%c' ", (short) ((format[ii].type << 24) >> 24));
printf("name: %s\ninfo: %s\nversion: %ld.%ld.%ld\n", name, info,
B_TRANSLATION_MAJOR_VER(version),
B_TRANSLATION_MINOR_VER(version),
B_TRANSLATION_REVSN_VER(version));
printf("(%c", (short) (format[ii].group >> 24));
printf("%c", (short) ((format[ii].group << 8) >> 24));
printf("%c", (short) ((format[ii].group << 16) >> 24));
printf("%c) ", (short) ((format[ii].group << 24) >> 24));
printf("%.1f ", format[ii].quality);
printf("%.1f ", format[ii].capability);
printf("%s ; ", format[ii].MIME);
printf("%s\n", format[ii].name);
for (int32 j = 0; j < inCount; j++) {
printf(" input:\t");
print_translation_format(outputFormats[j]);
}
for (int32 j = 0; j < outCount; j++) {
printf(" output:\t");
print_translation_format(outputFormats[j]);
}
printf("\n");
}
}
uint32 TranslateApp::GetTypecodeForOutputMime (const char * a_mime)
{
for (int32 i = 0; i < translators_total; i++)
{
const translation_format * format = NULL;
int32 format_num = 0;
translator_roster->GetOutputFormats(translator_array[i], & format, & format_num);
for (int32 ii = 0; ii < format_num; ii++)
{
if (! strcmp(a_mime, format[ii].MIME))
return (format[ii].type);
uint32
TranslateApp::GetTypeCodeForOutputMime(const char *mime)
{
for (int32 i = 0; i < fTranslatorCount; i++) {
const translation_format *format = NULL;
int32 count = 0;
fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &format, &count);
for (int32 ii = 0; ii < count; ii++) {
if (!strcmp(mime, format[ii].MIME))
return format[ii].type;
}
}
return 0;
}
void TranslateApp::RemoveOutputFile (char * path)
uint32
TranslateApp::GetTypeCodeFromString(const char *string)
{
BEntry entry (path);
entry.Remove();
size_t length = strlen(string);
if (length > 4)
return 0;
uint8 code[4] = {' ', ' ', ' ', ' '};
for (uint32 i = 0; i < length; i++)
code[i] = (uint8)string[i];
return B_HOST_TO_BENDIAN_INT32(*(uint32 *)code);
}
// #pragma mark -
int
main()
{
new TranslateApp();
be_app->Run();
return B_OK;
}