comparison OrthancServer/OrthancRestApi/OrthancRestArchive.cpp @ 2627:00b6a7f935fc jobs

refactoring of archive creation
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 25 May 2018 14:07:52 +0200
parents 878b59270859
children 7ba7d5806911
comparison
equal deleted inserted replaced
2626:e09021ddc00d 2627:00b6a7f935fc
56 // Download of ZIP files ---------------------------------------------------- 56 // Download of ZIP files ----------------------------------------------------
57 57
58 static bool IsZip64Required(uint64_t uncompressedSize, 58 static bool IsZip64Required(uint64_t uncompressedSize,
59 unsigned int countInstances) 59 unsigned int countInstances)
60 { 60 {
61 static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES; 61 static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES; // Should be large enough to hold DICOMDIR
62 static const unsigned int FILES_MARGIN = 10;
62 63
63 /** 64 /**
64 * Determine whether ZIP64 is required. Original ZIP format can 65 * Determine whether ZIP64 is required. Original ZIP format can
65 * store up to 2GB of data (some implementation supporting up to 66 * store up to 2GB of data (some implementation supporting up to
66 * 4GB of data), and up to 65535 files. 67 * 4GB of data), and up to 65535 files.
67 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 68 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64
68 **/ 69 **/
69 70
70 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN || 71 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN ||
71 countInstances >= 65535); 72 countInstances >= 65535 - FILES_MARGIN);
72 73
73 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " 74 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size "
74 << (uncompressedSize / MEGA_BYTES) << "MB using the " 75 << (uncompressedSize / MEGA_BYTES) << "MB using the "
75 << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; 76 << (isZip64 ? "ZIP64" : "ZIP32") << " file format";
76 77
78 } 79 }
79 80
80 81
81 namespace 82 namespace
82 { 83 {
83 class ResourceIdentifiers 84 class ResourceIdentifiers : public boost::noncopyable
84 { 85 {
85 private: 86 private:
86 ResourceType level_; 87 ResourceType level_;
87 std::string patient_; 88 std::string patient_;
88 std::string study_; 89 std::string study_;
191 virtual void AddInstance(const std::string& instanceId, 192 virtual void AddInstance(const std::string& instanceId,
192 const FileInfo& dicom) = 0; 193 const FileInfo& dicom) = 0;
193 }; 194 };
194 195
195 196
196 class ArchiveIndex 197 class ArchiveIndex : public boost::noncopyable
197 { 198 {
198 private: 199 private:
199 struct Instance 200 struct Instance
200 { 201 {
201 std::string id_; 202 std::string id_;
345 } 346 }
346 } 347 }
347 }; 348 };
348 349
349 350
350 class StatisticsVisitor : public IArchiveVisitor 351
352 class ArchiveCommands : public boost::noncopyable
351 { 353 {
352 private: 354 private:
353 uint64_t size_; 355 enum Type
354 unsigned int instances_; 356 {
357 Type_OpenDirectory,
358 Type_CloseDirectory,
359 Type_WriteInstance
360 };
361
362 class Command : public boost::noncopyable
363 {
364 private:
365 Type type_;
366 std::string filename_;
367 std::string instanceId_;
368 FileInfo info_;
369
370 public:
371 explicit Command(Type type) :
372 type_(type)
373 {
374 assert(type_ == Type_CloseDirectory);
375 }
376
377 Command(Type type,
378 const std::string& filename) :
379 type_(type),
380 filename_(filename)
381 {
382 assert(type_ == Type_OpenDirectory);
383 }
384
385 Command(Type type,
386 const std::string& filename,
387 const std::string& instanceId,
388 const FileInfo& info) :
389 type_(type),
390 filename_(filename),
391 instanceId_(instanceId),
392 info_(info)
393 {
394 assert(type_ == Type_WriteInstance);
395 }
396
397 void Apply(HierarchicalZipWriter& writer,
398 ServerContext& context,
399 DicomDirWriter* dicomDir,
400 const std::string& dicomDirFolder) const
401 {
402 switch (type_)
403 {
404 case Type_OpenDirectory:
405 writer.OpenDirectory(filename_.c_str());
406 break;
407
408 case Type_CloseDirectory:
409 writer.CloseDirectory();
410 break;
411
412 case Type_WriteInstance:
413 {
414 std::string content;
415
416 try
417 {
418 context.ReadAttachment(content, info_);
419 }
420 catch (OrthancException& e)
421 {
422 LOG(WARNING) << "An instance was removed after the job was issued: " << instanceId_;
423 return;
424 }
425
426 writer.OpenFile(filename_.c_str());
427 writer.Write(content);
428
429 if (dicomDir != NULL)
430 {
431 ParsedDicomFile parsed(content);
432 dicomDir->Add(dicomDirFolder, filename_, parsed);
433 }
434
435 break;
436 }
437
438 default:
439 throw OrthancException(ErrorCode_InternalError);
440 }
441 }
442 };
443
444 std::deque<Command*> commands_;
445 uint64_t uncompressedSize_;
446 unsigned int instancesCount_;
447
448
449 void ApplyInternal(HierarchicalZipWriter& writer,
450 ServerContext& context,
451 size_t index,
452 DicomDirWriter* dicomDir,
453 const std::string& dicomDirFolder) const
454 {
455 if (index >= commands_.size())
456 {
457 throw OrthancException(ErrorCode_ParameterOutOfRange);
458 }
459
460 commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder);
461 }
355 462
356 public: 463 public:
357 StatisticsVisitor() : size_(0), instances_(0) 464 ArchiveCommands() :
358 { 465 uncompressedSize_(0),
466 instancesCount_(0)
467 {
468 }
469
470 ~ArchiveCommands()
471 {
472 for (std::deque<Command*>::iterator it = commands_.begin();
473 it != commands_.end(); ++it)
474 {
475 assert(*it != NULL);
476 delete *it;
477 }
478 }
479
480 size_t GetSize() const
481 {
482 return commands_.size();
483 }
484
485 unsigned int GetInstancesCount() const
486 {
487 return instancesCount_;
359 } 488 }
360 489
361 uint64_t GetUncompressedSize() const 490 uint64_t GetUncompressedSize() const
362 { 491 {
363 return size_; 492 return uncompressedSize_;
364 } 493 }
365 494
366 unsigned int GetInstancesCount() const 495 void Apply(HierarchicalZipWriter& writer,
367 { 496 ServerContext& context,
368 return instances_; 497 size_t index,
369 } 498 DicomDirWriter& dicomDir,
370 499 const std::string& dicomDirFolder) const
371 virtual void Open(ResourceType level, 500 {
372 const std::string& publicId) 501 ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder);
373 { 502 }
374 } 503
375 504 void Apply(HierarchicalZipWriter& writer,
376 virtual void Close() 505 ServerContext& context,
377 { 506 size_t index) const
378 } 507 {
379 508 ApplyInternal(writer, context, index, NULL, "");
380 virtual void AddInstance(const std::string& instanceId, 509 }
381 const FileInfo& dicom) 510
382 { 511 void AddOpenDirectory(const std::string& filename)
383 instances_ ++; 512 {
384 size_ += dicom.GetUncompressedSize(); 513 commands_.push_back(new Command(Type_OpenDirectory, filename));
514 }
515
516 void AddCloseDirectory()
517 {
518 commands_.push_back(new Command(Type_CloseDirectory));
519 }
520
521 void AddWriteInstance(const std::string& filename,
522 const std::string& instanceId,
523 const FileInfo& info)
524 {
525 commands_.push_back(new Command(Type_WriteInstance, filename, instanceId, info));
526 instancesCount_ ++;
527 uncompressedSize_ += info.GetUncompressedSize();
528 }
529
530 bool IsZip64() const
531 {
532 return IsZip64Required(GetUncompressedSize(), GetInstancesCount());
385 } 533 }
386 }; 534 };
387 535
388 536
389 class PrintVisitor : public IArchiveVisitor 537
538 class ArchiveWriterVisitor : public IArchiveVisitor
390 { 539 {
391 private: 540 private:
392 std::ostream& out_; 541 ArchiveCommands& commands_;
393 std::string indent_; 542 ServerContext& context_;
394 543 char instanceFormat_[24];
395 public: 544 unsigned int counter_;
396 PrintVisitor(std::ostream& out) : out_(out)
397 {
398 }
399
400 virtual void Open(ResourceType level,
401 const std::string& publicId)
402 {
403 switch (level)
404 {
405 case ResourceType_Patient: indent_ = ""; break;
406 case ResourceType_Study: indent_ = " "; break;
407 case ResourceType_Series: indent_ = " "; break;
408 default:
409 throw OrthancException(ErrorCode_InternalError);
410 }
411
412 out_ << indent_ << publicId << std::endl;
413 }
414
415 virtual void Close()
416 {
417 }
418
419 virtual void AddInstance(const std::string& instanceId,
420 const FileInfo& dicom)
421 {
422 out_ << " " << instanceId << std::endl;
423 }
424 };
425
426
427 class ArchiveWriterVisitor : public IArchiveVisitor
428 {
429 private:
430 HierarchicalZipWriter& writer_;
431 ServerContext& context_;
432 char instanceFormat_[24];
433 unsigned int countInstances_;
434 545
435 static std::string GetTag(const DicomMap& tags, 546 static std::string GetTag(const DicomMap& tags,
436 const DicomTag& tag) 547 const DicomTag& tag)
437 { 548 {
438 const DicomValue* v = tags.TestAndGetValue(tag); 549 const DicomValue* v = tags.TestAndGetValue(tag);
447 return ""; 558 return "";
448 } 559 }
449 } 560 }
450 561
451 public: 562 public:
452 ArchiveWriterVisitor(HierarchicalZipWriter& writer, 563 ArchiveWriterVisitor(ArchiveCommands& commands,
453 ServerContext& context) : 564 ServerContext& context) :
454 writer_(writer), 565 commands_(commands),
455 context_(context), 566 context_(context),
456 countInstances_(0) 567 counter_(0)
457 { 568 {
569 if (commands.GetSize() != 0)
570 {
571 throw OrthancException(ErrorCode_BadSequenceOfCalls);
572 }
573
458 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); 574 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
459 } 575 }
460 576
461 virtual void Open(ResourceType level, 577 virtual void Open(ResourceType level,
462 const std::string& publicId) 578 const std::string& publicId)
494 { 610 {
495 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", 611 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm",
496 toupper(modality[0]), toupper(modality[1])); 612 toupper(modality[0]), toupper(modality[1]));
497 } 613 }
498 614
499 countInstances_ = 0; 615 counter_ = 0;
500 616
501 break; 617 break;
502 } 618 }
503 619
504 default: 620 default:
511 if (path.empty()) 627 if (path.empty())
512 { 628 {
513 path = std::string("Unknown ") + EnumerationToString(level); 629 path = std::string("Unknown ") + EnumerationToString(level);
514 } 630 }
515 631
516 writer_.OpenDirectory(path.c_str()); 632 commands_.AddOpenDirectory(path.c_str());
517 } 633 }
518 634
519 virtual void Close() 635 virtual void Close()
520 { 636 {
521 writer_.CloseDirectory(); 637 commands_.AddCloseDirectory();
522 } 638 }
523 639
524 virtual void AddInstance(const std::string& instanceId, 640 virtual void AddInstance(const std::string& instanceId,
525 const FileInfo& dicom) 641 const FileInfo& dicom)
526 { 642 {
527 std::string content;
528 context_.ReadAttachment(content, dicom);
529
530 char filename[24]; 643 char filename[24];
531 snprintf(filename, sizeof(filename) - 1, instanceFormat_, countInstances_); 644 snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_);
532 countInstances_ ++; 645 counter_ ++;
533 646
534 writer_.OpenFile(filename); 647 commands_.AddWriteInstance(filename, instanceId, dicom);
535 writer_.Write(content);
536 } 648 }
537 649
538 static void Apply(RestApiOutput& output, 650 static void Apply(RestApiOutput& output,
539 ServerContext& context, 651 ServerContext& context,
540 ArchiveIndex& archive, 652 ArchiveIndex& archive,
541 const std::string& filename) 653 const std::string& filename)
542 { 654 {
543 archive.Expand(context.GetIndex()); 655 ArchiveCommands commands;
544 656
545 StatisticsVisitor stats; 657 {
546 archive.Apply(stats); 658 ArchiveWriterVisitor visitor(commands, context);
547 659 archive.Expand(context.GetIndex());
548 const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount()); 660 archive.Apply(visitor);
661 }
549 662
550 // Create a RAII for the temporary file to manage the ZIP file 663 // Create a RAII for the temporary file to manage the ZIP file
551 TemporaryFile tmp; 664 TemporaryFile tmp;
552 665
553 { 666 {
554 // Create a ZIP writer
555 HierarchicalZipWriter writer(tmp.GetPath().c_str()); 667 HierarchicalZipWriter writer(tmp.GetPath().c_str());
556 writer.SetZip64(isZip64); 668 writer.SetZip64(commands.IsZip64());
557 669
558 ArchiveWriterVisitor v(writer, context); 670 for (size_t i = 0; i < commands.GetSize(); i++)
559 archive.Apply(v); 671 {
672 commands.Apply(writer, context, i);
673 }
560 } 674 }
561 675
562 // Prepare the sending of the ZIP file 676 // Prepare the sending of the ZIP file
563 FilesystemHttpSender sender(tmp.GetPath()); 677 FilesystemHttpSender sender(tmp.GetPath());
564 sender.SetContentType("application/zip"); 678 sender.SetContentType("application/zip");
573 687
574 688
575 class MediaWriterVisitor : public IArchiveVisitor 689 class MediaWriterVisitor : public IArchiveVisitor
576 { 690 {
577 private: 691 private:
578 HierarchicalZipWriter& writer_; 692 ArchiveCommands& commands_;
579 DicomDirWriter dicomDir_; 693 ServerContext& context_;
580 ServerContext& context_; 694 unsigned int counter_;
581 unsigned int countInstances_;
582 695
583 public: 696 public:
584 MediaWriterVisitor(HierarchicalZipWriter& writer, 697 MediaWriterVisitor(ArchiveCommands& commands,
585 ServerContext& context) : 698 ServerContext& context) :
586 writer_(writer), 699 commands_(commands),
587 context_(context), 700 context_(context),
588 countInstances_(0) 701 counter_(0)
589 { 702 {
590 }
591
592 void EncodeDicomDir(std::string& result)
593 {
594 dicomDir_.Encode(result);
595 } 703 }
596 704
597 virtual void Open(ResourceType level, 705 virtual void Open(ResourceType level,
598 const std::string& publicId) 706 const std::string& publicId)
599 { 707 {
607 const FileInfo& dicom) 715 const FileInfo& dicom)
608 { 716 {
609 // "DICOM restricts the filenames on DICOM media to 8 717 // "DICOM restricts the filenames on DICOM media to 8
610 // characters (some systems wrongly use 8.3, but this does not 718 // characters (some systems wrongly use 8.3, but this does not
611 // conform to the standard)." 719 // conform to the standard)."
612 std::string filename = "IM" + boost::lexical_cast<std::string>(countInstances_); 720 std::string filename = "IM" + boost::lexical_cast<std::string>(counter_);
613 writer_.OpenFile(filename.c_str()); 721 commands_.AddWriteInstance(filename, instanceId, dicom);
614 722
615 std::string content; 723 counter_ ++;
616 context_.ReadAttachment(content, dicom);
617 writer_.Write(content);
618
619 ParsedDicomFile parsed(content);
620 dicomDir_.Add("IMAGES", filename, parsed);
621
622 countInstances_ ++;
623 } 724 }
624 725
625 static void Apply(RestApiOutput& output, 726 static void Apply(RestApiOutput& output,
626 ServerContext& context, 727 ServerContext& context,
627 ArchiveIndex& archive, 728 ArchiveIndex& archive,
628 const std::string& filename, 729 const std::string& filename,
629 bool enableExtendedSopClass) 730 bool enableExtendedSopClass)
630 { 731 {
631 archive.Expand(context.GetIndex()); 732 static const char* IMAGES_FOLDER = "IMAGES";
632 733
633 StatisticsVisitor stats; 734 ArchiveCommands commands;
634 archive.Apply(stats); 735
635 736 {
636 const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount()); 737 MediaWriterVisitor visitor(commands, context);
738 archive.Expand(context.GetIndex());
739
740 commands.AddOpenDirectory(IMAGES_FOLDER);
741 archive.Apply(visitor);
742 commands.AddCloseDirectory();
743 }
637 744
638 // Create a RAII for the temporary file to manage the ZIP file 745 // Create a RAII for the temporary file to manage the ZIP file
639 TemporaryFile tmp; 746 TemporaryFile tmp;
640 747
641 { 748 {
642 // Create a ZIP writer 749 DicomDirWriter dicomDir;
750 dicomDir.EnableExtendedSopClass(enableExtendedSopClass);
751
643 HierarchicalZipWriter writer(tmp.GetPath().c_str()); 752 HierarchicalZipWriter writer(tmp.GetPath().c_str());
644 writer.SetZip64(isZip64); 753 writer.SetZip64(commands.IsZip64());
645 writer.OpenDirectory("IMAGES"); 754
646 755 for (size_t i = 0; i < commands.GetSize(); i++)
647 // Create a DICOMDIR writer 756 {
648 MediaWriterVisitor v(writer, context); 757 commands.Apply(writer, context, i, dicomDir, IMAGES_FOLDER);
649 758 }
650 // Request type-3 arguments to be added to the DICOMDIR
651 v.dicomDir_.EnableExtendedSopClass(enableExtendedSopClass);
652
653 archive.Apply(v);
654 759
655 // Add the DICOMDIR 760 // Add the DICOMDIR
656 writer.CloseDirectory();
657 writer.OpenFile("DICOMDIR"); 761 writer.OpenFile("DICOMDIR");
658 std::string s; 762 std::string s;
659 v.EncodeDicomDir(s); 763 dicomDir.Encode(s);
660 writer.Write(s); 764 writer.Write(s);
661 } 765 }
766
662 767
663 // Prepare the sending of the ZIP file 768 // Prepare the sending of the ZIP file
664 FilesystemHttpSender sender(tmp.GetPath()); 769 FilesystemHttpSender sender(tmp.GetPath());
665 sender.SetContentType("application/zip"); 770 sender.SetContentType("application/zip");
666 sender.SetContentFilename(filename); 771 sender.SetContentFilename(filename);
669 output.AnswerStream(sender); 774 output.AnswerStream(sender);
670 775
671 // The temporary file is automatically removed thanks to the RAII 776 // The temporary file is automatically removed thanks to the RAII
672 } 777 }
673 }; 778 };
779
780
781 #if 0
782 class ArchiveJob : public IJob
783 {
784 private:
785 ServerContext& context_;
786 ArchiveIndex archive_;
787 bool isMedia_;
788 std::auto_ptr<TemporaryFile> file_;
789 std::auto_ptr<HierarchicalZipWriter> writer_;
790 std::auto_ptr<IArchiveVisitor> visitor_;
791 std::string description_;
792 bool started_;
793 unsigned int currentInstance_;
794
795 public:
796 ArchiveJob(ServerContext& context,
797 ResourceType level,
798 bool isMedia) :
799 context_(context),
800 archive_(level),
801 isMedia_(isMedia),
802 started_(false),
803 currentInstance_(0)
804 {
805 }
806
807 void SetDescription(const std::string& description)
808 {
809 description_ = description;
810 }
811
812 const std::string& GetDescription() const
813 {
814 return description_;
815 }
816
817 void AddResource(const std::string& publicId)
818 {
819 if (started_)
820 {
821 throw OrthancException(ErrorCode_BadSequenceOfCalls);
822 }
823
824 ResourceIdentifiers resource(context_.GetIndex(), publicId);
825 archive_.Add(context_.GetIndex(), resource);
826 }
827
828 virtual void SignalResubmit()
829 {
830 LOG(ERROR) << "Cannot resubmit the creation of an archive";
831 throw OrthancException(ErrorCode_BadSequenceOfCalls);
832 }
833
834 virtual void Start()
835 {
836 if (started_)
837 {
838 throw OrthancException(ErrorCode_BadSequenceOfCalls);
839 }
840
841 started_ = true;
842
843 archive_.Expand(context_.GetIndex());
844
845 const bool isZip64 = IsZip64Required(stats_.GetUncompressedSize(), stats_.GetInstancesCount());
846
847 file_.reset(new TemporaryFile);
848 writer_.reset(new HierarchicalZipWriter(file_->GetPath().c_str()));
849 writer_->SetZip64(isZip64);
850
851 if (isMedia_)
852 {
853 //visitor_.reset(new
854 }
855 }
856 };
857 #endif
674 } 858 }
675 859
676 860
677 static bool AddResourcesOfInterest(ArchiveIndex& archive, 861 static bool AddResourcesOfInterest(ArchiveIndex& archive,
678 RestApiPostCall& call) 862 RestApiPostCall& call)