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 <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <ctype.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 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 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 132 TypeList::Add(uint32 type) 133 { 134 fList.AddItem((void *)(addr_t)type, 0); 135 } 136 137 138 bool 139 TypeList::Remove(uint32 type) 140 { 141 return fList.RemoveItem((void *)(addr_t)type); 142 } 143 144 145 bool 146 TypeList::FindType(uint32 type) 147 { 148 return fList.IndexOf((void *)(addr_t)type) >= 0; 149 } 150 151 152 void 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 163 TypeList::Count() 164 { 165 return fList.CountItems(); 166 } 167 168 169 uint32 170 TypeList::TypeAt(int32 index) 171 { 172 return (uint32)(addr_t)fList.ItemAt(index); 173 } 174 175 176 // #pragma mark - 177 178 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 186 RemovingFile::~RemovingFile() 187 { 188 fEntry.Remove(); 189 } 190 191 192 void 193 RemovingFile::Keep() 194 { 195 fEntry.Unset(); 196 } 197 198 199 // #pragma mark - 200 201 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 212 Translator::~Translator() 213 { 214 } 215 216 217 status_t 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 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 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 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 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 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 498 TranslateApp::~TranslateApp() 499 { 500 delete[] fTranslatorArray; 501 } 502 503 504 void 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 558 TranslateApp::ReadyToRun(void) 559 { 560 if (fGotArguments == false) 561 PrintUsage(); 562 563 PostMessage(B_QUIT_REQUESTED); 564 } 565 566 567 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 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 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 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 674 main() 675 { 676 new TranslateApp(); 677 be_app->Run(); 678 679 return B_OK; 680 } 681 682