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