comparison OrthancServer/OrthancRestApi/OrthancRestArchive.cpp @ 2632:2406ae891747 jobs

ArchiveJob
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 25 May 2018 18:09:27 +0200
parents 00327e989458
children 251614c2edac
comparison
equal deleted inserted replaced
2631:a963b5e2d963 2632:2406ae891747
32 32
33 33
34 #include "../PrecompiledHeadersServer.h" 34 #include "../PrecompiledHeadersServer.h"
35 #include "OrthancRestApi.h" 35 #include "OrthancRestApi.h"
36 36
37 #include "../../Core/DicomParsing/DicomDirWriter.h"
38 #include "../../Core/FileStorage/StorageAccessor.h"
39 #include "../../Core/Compression/HierarchicalZipWriter.h"
40 #include "../../Core/HttpServer/FilesystemHttpSender.h" 37 #include "../../Core/HttpServer/FilesystemHttpSender.h"
41 #include "../../Core/Logging.h" 38 #include "../ServerJobs/ArchiveJob.h"
42 #include "../../Core/TemporaryFile.h"
43 #include "../ServerContext.h"
44
45 #include <stdio.h>
46
47 #if defined(_MSC_VER)
48 #define snprintf _snprintf
49 #endif
50
51 static const uint64_t MEGA_BYTES = 1024 * 1024;
52 static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024;
53 39
54 namespace Orthanc 40 namespace Orthanc
55 { 41 {
56 // Download of ZIP files ---------------------------------------------------- 42 static bool AddResourcesOfInterest(ArchiveJob& job,
57
58 static bool IsZip64Required(uint64_t uncompressedSize,
59 unsigned int countInstances)
60 {
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;
63
64 /**
65 * Determine whether ZIP64 is required. Original ZIP format can
66 * store up to 2GB of data (some implementation supporting up to
67 * 4GB of data), and up to 65535 files.
68 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64
69 **/
70
71 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN ||
72 countInstances >= 65535 - FILES_MARGIN);
73
74 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size "
75 << (uncompressedSize / MEGA_BYTES) << "MB using the "
76 << (isZip64 ? "ZIP64" : "ZIP32") << " file format";
77
78 return isZip64;
79 }
80
81
82 namespace
83 {
84 class ResourceIdentifiers : public boost::noncopyable
85 {
86 private:
87 ResourceType level_;
88 std::string patient_;
89 std::string study_;
90 std::string series_;
91 std::string instance_;
92
93 static void GoToParent(ServerIndex& index,
94 std::string& current)
95 {
96 std::string tmp;
97
98 if (index.LookupParent(tmp, current))
99 {
100 current = tmp;
101 }
102 else
103 {
104 throw OrthancException(ErrorCode_UnknownResource);
105 }
106 }
107
108
109 public:
110 ResourceIdentifiers(ServerIndex& index,
111 const std::string& publicId)
112 {
113 if (!index.LookupResourceType(level_, publicId))
114 {
115 throw OrthancException(ErrorCode_UnknownResource);
116 }
117
118 std::string current = publicId;;
119 switch (level_) // Do not add "break" below!
120 {
121 case ResourceType_Instance:
122 instance_ = current;
123 GoToParent(index, current);
124
125 case ResourceType_Series:
126 series_ = current;
127 GoToParent(index, current);
128
129 case ResourceType_Study:
130 study_ = current;
131 GoToParent(index, current);
132
133 case ResourceType_Patient:
134 patient_ = current;
135 break;
136
137 default:
138 throw OrthancException(ErrorCode_InternalError);
139 }
140 }
141
142 ResourceType GetLevel() const
143 {
144 return level_;
145 }
146
147 const std::string& GetIdentifier(ResourceType level) const
148 {
149 // Some sanity check to ensure enumerations are not altered
150 assert(ResourceType_Patient < ResourceType_Study);
151 assert(ResourceType_Study < ResourceType_Series);
152 assert(ResourceType_Series < ResourceType_Instance);
153
154 if (level > level_)
155 {
156 throw OrthancException(ErrorCode_InternalError);
157 }
158
159 switch (level)
160 {
161 case ResourceType_Patient:
162 return patient_;
163
164 case ResourceType_Study:
165 return study_;
166
167 case ResourceType_Series:
168 return series_;
169
170 case ResourceType_Instance:
171 return instance_;
172
173 default:
174 throw OrthancException(ErrorCode_InternalError);
175 }
176 }
177 };
178
179
180 class IArchiveVisitor : public boost::noncopyable
181 {
182 public:
183 virtual ~IArchiveVisitor()
184 {
185 }
186
187 virtual void Open(ResourceType level,
188 const std::string& publicId) = 0;
189
190 virtual void Close() = 0;
191
192 virtual void AddInstance(const std::string& instanceId,
193 const FileInfo& dicom) = 0;
194 };
195
196
197 class ArchiveIndex : public boost::noncopyable
198 {
199 private:
200 struct Instance
201 {
202 std::string id_;
203 FileInfo dicom_;
204
205 Instance(const std::string& id,
206 const FileInfo& dicom) :
207 id_(id), dicom_(dicom)
208 {
209 }
210 };
211
212 // A "NULL" value for ArchiveIndex indicates a non-expanded node
213 typedef std::map<std::string, ArchiveIndex*> Resources;
214
215 ResourceType level_;
216 Resources resources_; // Only at patient/study/series level
217 std::list<Instance> instances_; // Only at instance level
218
219
220 void AddResourceToExpand(ServerIndex& index,
221 const std::string& id)
222 {
223 if (level_ == ResourceType_Instance)
224 {
225 FileInfo tmp;
226 if (index.LookupAttachment(tmp, id, FileContentType_Dicom))
227 {
228 instances_.push_back(Instance(id, tmp));
229 }
230 }
231 else
232 {
233 resources_[id] = NULL;
234 }
235 }
236
237
238 public:
239 ArchiveIndex(ResourceType level) :
240 level_(level)
241 {
242 }
243
244 ~ArchiveIndex()
245 {
246 for (Resources::iterator it = resources_.begin();
247 it != resources_.end(); ++it)
248 {
249 delete it->second;
250 }
251 }
252
253
254 void Add(ServerIndex& index,
255 const ResourceIdentifiers& resource)
256 {
257 const std::string& id = resource.GetIdentifier(level_);
258 Resources::iterator previous = resources_.find(id);
259
260 if (level_ == ResourceType_Instance)
261 {
262 AddResourceToExpand(index, id);
263 }
264 else if (resource.GetLevel() == level_)
265 {
266 // Mark this resource for further expansion
267 if (previous != resources_.end())
268 {
269 delete previous->second;
270 }
271
272 resources_[id] = NULL;
273 }
274 else if (previous == resources_.end())
275 {
276 // This is the first time we meet this resource
277 std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
278 child->Add(index, resource);
279 resources_[id] = child.release();
280 }
281 else if (previous->second != NULL)
282 {
283 previous->second->Add(index, resource);
284 }
285 else
286 {
287 // Nothing to do: This item is marked for further expansion
288 }
289 }
290
291
292 void Expand(ServerIndex& index)
293 {
294 if (level_ == ResourceType_Instance)
295 {
296 // Expanding an instance node makes no sense
297 return;
298 }
299
300 for (Resources::iterator it = resources_.begin();
301 it != resources_.end(); ++it)
302 {
303 if (it->second == NULL)
304 {
305 // This is resource is marked for expansion
306 std::list<std::string> children;
307 index.GetChildren(children, it->first);
308
309 std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
310
311 for (std::list<std::string>::const_iterator
312 it2 = children.begin(); it2 != children.end(); ++it2)
313 {
314 child->AddResourceToExpand(index, *it2);
315 }
316
317 it->second = child.release();
318 }
319
320 assert(it->second != NULL);
321 it->second->Expand(index);
322 }
323 }
324
325
326 void Apply(IArchiveVisitor& visitor) const
327 {
328 if (level_ == ResourceType_Instance)
329 {
330 for (std::list<Instance>::const_iterator
331 it = instances_.begin(); it != instances_.end(); ++it)
332 {
333 visitor.AddInstance(it->id_, it->dicom_);
334 }
335 }
336 else
337 {
338 for (Resources::const_iterator it = resources_.begin();
339 it != resources_.end(); ++it)
340 {
341 assert(it->second != NULL); // There must have been a call to "Expand()"
342 visitor.Open(level_, it->first);
343 it->second->Apply(visitor);
344 visitor.Close();
345 }
346 }
347 }
348 };
349
350
351
352 class ZipCommands : public boost::noncopyable
353 {
354 private:
355 enum Type
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 }
462
463 public:
464 ZipCommands() :
465 uncompressedSize_(0),
466 instancesCount_(0)
467 {
468 }
469
470 ~ZipCommands()
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_;
488 }
489
490 uint64_t GetUncompressedSize() const
491 {
492 return uncompressedSize_;
493 }
494
495 void Apply(HierarchicalZipWriter& writer,
496 ServerContext& context,
497 size_t index,
498 DicomDirWriter& dicomDir,
499 const std::string& dicomDirFolder) const
500 {
501 ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder);
502 }
503
504 void Apply(HierarchicalZipWriter& writer,
505 ServerContext& context,
506 size_t index) const
507 {
508 ApplyInternal(writer, context, index, NULL, "");
509 }
510
511 void AddOpenDirectory(const std::string& filename)
512 {
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());
533 }
534 };
535
536
537
538 class ArchiveIndexVisitor : public IArchiveVisitor
539 {
540 private:
541 ZipCommands& commands_;
542 ServerContext& context_;
543 char instanceFormat_[24];
544 unsigned int counter_;
545
546 static std::string GetTag(const DicomMap& tags,
547 const DicomTag& tag)
548 {
549 const DicomValue* v = tags.TestAndGetValue(tag);
550 if (v != NULL &&
551 !v->IsBinary() &&
552 !v->IsNull())
553 {
554 return v->GetContent();
555 }
556 else
557 {
558 return "";
559 }
560 }
561
562 public:
563 ArchiveIndexVisitor(ZipCommands& commands,
564 ServerContext& context) :
565 commands_(commands),
566 context_(context),
567 counter_(0)
568 {
569 if (commands.GetSize() != 0)
570 {
571 throw OrthancException(ErrorCode_BadSequenceOfCalls);
572 }
573
574 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
575 }
576
577 virtual void Open(ResourceType level,
578 const std::string& publicId)
579 {
580 std::string path;
581
582 DicomMap tags;
583 if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level))
584 {
585 switch (level)
586 {
587 case ResourceType_Patient:
588 path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME);
589 break;
590
591 case ResourceType_Study:
592 path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION);
593 break;
594
595 case ResourceType_Series:
596 {
597 std::string modality = GetTag(tags, DICOM_TAG_MODALITY);
598 path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION);
599
600 if (modality.size() == 0)
601 {
602 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
603 }
604 else if (modality.size() == 1)
605 {
606 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm",
607 toupper(modality[0]));
608 }
609 else if (modality.size() >= 2)
610 {
611 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm",
612 toupper(modality[0]), toupper(modality[1]));
613 }
614
615 counter_ = 0;
616
617 break;
618 }
619
620 default:
621 throw OrthancException(ErrorCode_InternalError);
622 }
623 }
624
625 path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path));
626
627 if (path.empty())
628 {
629 path = std::string("Unknown ") + EnumerationToString(level);
630 }
631
632 commands_.AddOpenDirectory(path.c_str());
633 }
634
635 virtual void Close()
636 {
637 commands_.AddCloseDirectory();
638 }
639
640 virtual void AddInstance(const std::string& instanceId,
641 const FileInfo& dicom)
642 {
643 char filename[24];
644 snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_);
645 counter_ ++;
646
647 commands_.AddWriteInstance(filename, instanceId, dicom);
648 }
649 };
650
651
652 class MediaIndexVisitor : public IArchiveVisitor
653 {
654 private:
655 ZipCommands& commands_;
656 ServerContext& context_;
657 unsigned int counter_;
658
659 public:
660 MediaIndexVisitor(ZipCommands& commands,
661 ServerContext& context) :
662 commands_(commands),
663 context_(context),
664 counter_(0)
665 {
666 }
667
668 virtual void Open(ResourceType level,
669 const std::string& publicId)
670 {
671 }
672
673 virtual void Close()
674 {
675 }
676
677 virtual void AddInstance(const std::string& instanceId,
678 const FileInfo& dicom)
679 {
680 // "DICOM restricts the filenames on DICOM media to 8
681 // characters (some systems wrongly use 8.3, but this does not
682 // conform to the standard)."
683 std::string filename = "IM" + boost::lexical_cast<std::string>(counter_);
684 commands_.AddWriteInstance(filename, instanceId, dicom);
685
686 counter_ ++;
687 }
688 };
689
690
691 static const char* IMAGES_FOLDER = "IMAGES";
692
693
694 class ZipWriterIterator : public boost::noncopyable
695 {
696 private:
697 TemporaryFile& target_;
698 ServerContext& context_;
699 ZipCommands commands_;
700 std::auto_ptr<HierarchicalZipWriter> zip_;
701 std::auto_ptr<DicomDirWriter> dicomDir_;
702 bool isMedia_;
703
704 public:
705 ZipWriterIterator(TemporaryFile& target,
706 ServerContext& context,
707 ArchiveIndex& archive,
708 bool isMedia,
709 bool enableExtendedSopClass) :
710 target_(target),
711 context_(context),
712 isMedia_(isMedia)
713 {
714 if (isMedia)
715 {
716 MediaIndexVisitor visitor(commands_, context);
717 archive.Expand(context.GetIndex());
718
719 commands_.AddOpenDirectory(IMAGES_FOLDER);
720 archive.Apply(visitor);
721 commands_.AddCloseDirectory();
722
723 dicomDir_.reset(new DicomDirWriter);
724 dicomDir_->EnableExtendedSopClass(enableExtendedSopClass);
725 }
726 else
727 {
728 ArchiveIndexVisitor visitor(commands_, context);
729 archive.Expand(context.GetIndex());
730 archive.Apply(visitor);
731 }
732
733 zip_.reset(new HierarchicalZipWriter(target.GetPath().c_str()));
734 zip_->SetZip64(commands_.IsZip64());
735 }
736
737 size_t GetStepsCount() const
738 {
739 return commands_.GetSize() + 1;
740 }
741
742 void RunStep(size_t index)
743 {
744 if (index > commands_.GetSize())
745 {
746 throw OrthancException(ErrorCode_ParameterOutOfRange);
747 }
748 else if (index == commands_.GetSize())
749 {
750 // Last step: Add the DICOMDIR
751 if (isMedia_)
752 {
753 assert(dicomDir_.get() != NULL);
754 std::string s;
755 dicomDir_->Encode(s);
756
757 zip_->OpenFile("DICOMDIR");
758 zip_->Write(s);
759 }
760 }
761 else
762 {
763 if (isMedia_)
764 {
765 assert(dicomDir_.get() != NULL);
766 commands_.Apply(*zip_, context_, index, *dicomDir_, IMAGES_FOLDER);
767 }
768 else
769 {
770 assert(dicomDir_.get() == NULL);
771 commands_.Apply(*zip_, context_, index);
772 }
773 }
774 }
775
776 unsigned int GetInstancesCount() const
777 {
778 return commands_.GetInstancesCount();
779 }
780
781 uint64_t GetUncompressedSize() const
782 {
783 return commands_.GetUncompressedSize();
784 }
785 };
786
787
788 class ArchiveJob : public IJob
789 {
790 private:
791 boost::shared_ptr<TemporaryFile> target_;
792 ServerContext& context_;
793 ArchiveIndex archive_;
794 bool isMedia_;
795 bool enableExtendedSopClass_;
796 std::string description_;
797
798 std::auto_ptr<ZipWriterIterator> writer_;
799 size_t currentStep_;
800 unsigned int instancesCount_;
801 uint64_t uncompressedSize_;
802
803 public:
804 ArchiveJob(boost::shared_ptr<TemporaryFile>& target,
805 ServerContext& context,
806 bool isMedia,
807 bool enableExtendedSopClass) :
808 target_(target),
809 context_(context),
810 archive_(ResourceType_Patient), // root
811 isMedia_(isMedia),
812 enableExtendedSopClass_(enableExtendedSopClass),
813 currentStep_(0),
814 instancesCount_(0),
815 uncompressedSize_(0)
816 {
817 if (target.get() == NULL)
818 {
819 throw OrthancException(ErrorCode_NullPointer);
820 }
821 }
822
823 ArchiveIndex& GetIndex()
824 {
825 if (writer_.get() != NULL) // Already started
826 {
827 throw OrthancException(ErrorCode_BadSequenceOfCalls);
828 }
829
830 return archive_;
831 }
832
833 void SetDescription(const std::string& description)
834 {
835 description_ = description;
836 }
837
838 const std::string& GetDescription() const
839 {
840 return description_;
841 }
842
843 void AddResource(const std::string& publicId)
844 {
845 if (writer_.get() != NULL) // Already started
846 {
847 throw OrthancException(ErrorCode_BadSequenceOfCalls);
848 }
849
850 ResourceIdentifiers resource(context_.GetIndex(), publicId);
851 archive_.Add(context_.GetIndex(), resource);
852 }
853
854 virtual void SignalResubmit()
855 {
856 LOG(ERROR) << "Cannot resubmit the creation of an archive";
857 throw OrthancException(ErrorCode_BadSequenceOfCalls);
858 }
859
860 virtual void Start()
861 {
862 if (writer_.get() != NULL)
863 {
864 throw OrthancException(ErrorCode_BadSequenceOfCalls);
865 }
866
867 writer_.reset(new ZipWriterIterator(*target_, context_, archive_, isMedia_, enableExtendedSopClass_));
868
869 instancesCount_ = writer_->GetInstancesCount();
870 uncompressedSize_ = writer_->GetUncompressedSize();
871 }
872
873 virtual JobStepResult ExecuteStep()
874 {
875 assert(writer_.get() != NULL);
876
877 if (target_.unique())
878 {
879 LOG(WARNING) << "A client has disconnected while creating an archive";
880 return JobStepResult::Failure(ErrorCode_NetworkProtocol);
881 }
882
883 if (writer_->GetStepsCount() == 0)
884 {
885 writer_.reset(NULL); // Flush all the results
886 return JobStepResult::Success();
887 }
888 else
889 {
890 writer_->RunStep(currentStep_);
891
892 currentStep_ ++;
893
894 if (currentStep_ == writer_->GetStepsCount())
895 {
896 writer_.reset(NULL); // Flush all the results
897 return JobStepResult::Success();
898 }
899 else
900 {
901 return JobStepResult::Continue();
902 }
903 }
904 }
905
906 virtual void ReleaseResources()
907 {
908 }
909
910 virtual float GetProgress()
911 {
912 if (writer_.get() == NULL ||
913 writer_->GetStepsCount() == 0)
914 {
915 return 1;
916 }
917 else
918 {
919 return (static_cast<float>(currentStep_) /
920 static_cast<float>(writer_->GetStepsCount() - 1));
921 }
922 }
923
924 virtual void GetJobType(std::string& target)
925 {
926 if (isMedia_)
927 {
928 target = "Media";
929 }
930 else
931 {
932 target = "Archive";
933 }
934 }
935
936 virtual void GetPublicContent(Json::Value& value)
937 {
938 value["Description"] = description_;
939 value["InstancesCount"] = instancesCount_;
940 value["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize_ / (1024llu * 1024llu));
941 }
942
943 virtual void GetInternalContent(Json::Value& value)
944 {
945 }
946 };
947 }
948
949
950 static bool AddResourcesOfInterest(ArchiveIndex& archive,
951 RestApiPostCall& call) 43 RestApiPostCall& call)
952 { 44 {
953 ServerIndex& index = OrthancRestApi::GetIndex(call);
954
955 Json::Value resources; 45 Json::Value resources;
956 if (call.ParseJsonRequest(resources) && 46 if (call.ParseJsonRequest(resources) &&
957 resources.type() == Json::arrayValue) 47 resources.type() == Json::arrayValue)
958 { 48 {
959 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) 49 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
961 if (resources[i].type() != Json::stringValue) 51 if (resources[i].type() != Json::stringValue)
962 { 52 {
963 return false; // Bad request 53 return false; // Bad request
964 } 54 }
965 55
966 ResourceIdentifiers resource(index, resources[i].asString()); 56 job.AddResource(resources[i].asString());
967 archive.Add(index, resource);
968 } 57 }
969 58
970 return true; 59 return true;
971 } 60 }
972 else 61 else
1011 ServerContext& context = OrthancRestApi::GetContext(call); 100 ServerContext& context = OrthancRestApi::GetContext(call);
1012 101
1013 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); 102 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile);
1014 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false)); 103 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false));
1015 104
1016 if (AddResourcesOfInterest(job->GetIndex(), call)) 105 if (AddResourcesOfInterest(*job, call))
1017 { 106 {
1018 SubmitJob(call, tmp, context, job, "Archive.zip"); 107 SubmitJob(call, tmp, context, job, "Archive.zip");
1019 } 108 }
1020 } 109 }
1021 110
1026 ServerContext& context = OrthancRestApi::GetContext(call); 115 ServerContext& context = OrthancRestApi::GetContext(call);
1027 116
1028 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); 117 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile);
1029 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, Extended)); 118 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, Extended));
1030 119
1031 if (AddResourcesOfInterest(job->GetIndex(), call)) 120 if (AddResourcesOfInterest(*job, call))
1032 { 121 {
1033 SubmitJob(call, tmp, context, job, "Archive.zip"); 122 SubmitJob(call, tmp, context, job, "Archive.zip");
1034 } 123 }
1035 } 124 }
1036 125
1038 static void CreateArchive(RestApiGetCall& call) 127 static void CreateArchive(RestApiGetCall& call)
1039 { 128 {
1040 ServerContext& context = OrthancRestApi::GetContext(call); 129 ServerContext& context = OrthancRestApi::GetContext(call);
1041 130
1042 std::string id = call.GetUriComponent("id", ""); 131 std::string id = call.GetUriComponent("id", "");
1043 ResourceIdentifiers resource(context.GetIndex(), id);
1044 132
1045 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); 133 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile);
1046 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false)); 134 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, false, false));
1047 job->GetIndex().Add(OrthancRestApi::GetIndex(call), resource); 135 job->AddResource(id);
1048 136
1049 SubmitJob(call, tmp, context, job, id + ".zip"); 137 SubmitJob(call, tmp, context, job, id + ".zip");
1050 } 138 }
1051 139
1052 140
1053 static void CreateMedia(RestApiGetCall& call) 141 static void CreateMedia(RestApiGetCall& call)
1054 { 142 {
1055 ServerContext& context = OrthancRestApi::GetContext(call); 143 ServerContext& context = OrthancRestApi::GetContext(call);
1056 144
1057 std::string id = call.GetUriComponent("id", ""); 145 std::string id = call.GetUriComponent("id", "");
1058 ResourceIdentifiers resource(context.GetIndex(), id);
1059 146
1060 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile); 147 boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile);
1061 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, call.HasArgument("extended"))); 148 std::auto_ptr<ArchiveJob> job(new ArchiveJob(tmp, context, true, call.HasArgument("extended")));
1062 job->GetIndex().Add(OrthancRestApi::GetIndex(call), resource); 149 job->AddResource(id);
1063 150
1064 SubmitJob(call, tmp, context, job, id + ".zip"); 151 SubmitJob(call, tmp, context, job, id + ".zip");
1065 } 152 }
1066 153
1067 154