From 9324698b193ed49e2b71b489a49a370f469e6807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Fri, 4 Feb 2005 04:04:52 +0000 Subject: [PATCH] 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 --- src/apps/bin/translate.cpp | 770 +++++++++++++++++++++++++++---------- 1 file changed, 570 insertions(+), 200 deletions(-) diff --git a/src/apps/bin/translate.cpp b/src/apps/bin/translate.cpp index 5b074e8a45..f98e174def 100644 --- a/src/apps/bin/translate.cpp +++ b/src/apps/bin/translate.cpp @@ -1,266 +1,636 @@ -// translate - for OpenBeOS -// -// authors - in order of contribution -// jonas.sundstrom@kirilla.com -// -// 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, axeld@pinc-software.de + * jonas.sundstrom@kirilla.com + */ -#include -#include #include #include #include #include +#include +#include #include - -class TranslateApp : public BApplication -{ - public: - TranslateApp (void); - virtual void ReadyToRun (void); - virtual void ArgvReceived (int32 argc, char **argv); +#include +#include +#include + + +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; +} +