Mercurial > hg > orthanc
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 |