Mercurial > hg > orthanc
comparison OrthancServer/ServerJobs/ArchiveJob.cpp @ 2632:2406ae891747 jobs
ArchiveJob
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 25 May 2018 18:09:27 +0200 |
parents | |
children | c72eb844758c |
comparison
equal
deleted
inserted
replaced
2631:a963b5e2d963 | 2632:2406ae891747 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2018 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeadersServer.h" | |
35 #include "ArchiveJob.h" | |
36 | |
37 #include "../../Core/Compression/HierarchicalZipWriter.h" | |
38 #include "../../Core/DicomParsing/DicomDirWriter.h" | |
39 #include "../../Core/Logging.h" | |
40 #include "../../Core/OrthancException.h" | |
41 | |
42 #include <stdio.h> | |
43 | |
44 #if defined(_MSC_VER) | |
45 #define snprintf _snprintf | |
46 #endif | |
47 | |
48 static const uint64_t MEGA_BYTES = 1024 * 1024; | |
49 static const uint64_t GIGA_BYTES = 1024 * 1024 * 1024; | |
50 static const char* MEDIA_IMAGES_FOLDER = "IMAGES"; | |
51 | |
52 namespace Orthanc | |
53 { | |
54 static bool IsZip64Required(uint64_t uncompressedSize, | |
55 unsigned int countInstances) | |
56 { | |
57 static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES; // Should be large enough to hold DICOMDIR | |
58 static const unsigned int FILES_MARGIN = 10; | |
59 | |
60 /** | |
61 * Determine whether ZIP64 is required. Original ZIP format can | |
62 * store up to 2GB of data (some implementation supporting up to | |
63 * 4GB of data), and up to 65535 files. | |
64 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 | |
65 **/ | |
66 | |
67 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN || | |
68 countInstances >= 65535 - FILES_MARGIN); | |
69 | |
70 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size " | |
71 << (uncompressedSize / MEGA_BYTES) << "MB using the " | |
72 << (isZip64 ? "ZIP64" : "ZIP32") << " file format"; | |
73 | |
74 return isZip64; | |
75 } | |
76 | |
77 | |
78 class ArchiveJob::ResourceIdentifiers : public boost::noncopyable | |
79 { | |
80 private: | |
81 ResourceType level_; | |
82 std::string patient_; | |
83 std::string study_; | |
84 std::string series_; | |
85 std::string instance_; | |
86 | |
87 static void GoToParent(ServerIndex& index, | |
88 std::string& current) | |
89 { | |
90 std::string tmp; | |
91 | |
92 if (index.LookupParent(tmp, current)) | |
93 { | |
94 current = tmp; | |
95 } | |
96 else | |
97 { | |
98 throw OrthancException(ErrorCode_UnknownResource); | |
99 } | |
100 } | |
101 | |
102 | |
103 public: | |
104 ResourceIdentifiers(ServerIndex& index, | |
105 const std::string& publicId) | |
106 { | |
107 if (!index.LookupResourceType(level_, publicId)) | |
108 { | |
109 throw OrthancException(ErrorCode_UnknownResource); | |
110 } | |
111 | |
112 std::string current = publicId;; | |
113 switch (level_) // Do not add "break" below! | |
114 { | |
115 case ResourceType_Instance: | |
116 instance_ = current; | |
117 GoToParent(index, current); | |
118 | |
119 case ResourceType_Series: | |
120 series_ = current; | |
121 GoToParent(index, current); | |
122 | |
123 case ResourceType_Study: | |
124 study_ = current; | |
125 GoToParent(index, current); | |
126 | |
127 case ResourceType_Patient: | |
128 patient_ = current; | |
129 break; | |
130 | |
131 default: | |
132 throw OrthancException(ErrorCode_InternalError); | |
133 } | |
134 } | |
135 | |
136 ResourceType GetLevel() const | |
137 { | |
138 return level_; | |
139 } | |
140 | |
141 const std::string& GetIdentifier(ResourceType level) const | |
142 { | |
143 // Some sanity check to ensure enumerations are not altered | |
144 assert(ResourceType_Patient < ResourceType_Study); | |
145 assert(ResourceType_Study < ResourceType_Series); | |
146 assert(ResourceType_Series < ResourceType_Instance); | |
147 | |
148 if (level > level_) | |
149 { | |
150 throw OrthancException(ErrorCode_InternalError); | |
151 } | |
152 | |
153 switch (level) | |
154 { | |
155 case ResourceType_Patient: | |
156 return patient_; | |
157 | |
158 case ResourceType_Study: | |
159 return study_; | |
160 | |
161 case ResourceType_Series: | |
162 return series_; | |
163 | |
164 case ResourceType_Instance: | |
165 return instance_; | |
166 | |
167 default: | |
168 throw OrthancException(ErrorCode_InternalError); | |
169 } | |
170 } | |
171 }; | |
172 | |
173 | |
174 class ArchiveJob::IArchiveVisitor : public boost::noncopyable | |
175 { | |
176 public: | |
177 virtual ~IArchiveVisitor() | |
178 { | |
179 } | |
180 | |
181 virtual void Open(ResourceType level, | |
182 const std::string& publicId) = 0; | |
183 | |
184 virtual void Close() = 0; | |
185 | |
186 virtual void AddInstance(const std::string& instanceId, | |
187 const FileInfo& dicom) = 0; | |
188 }; | |
189 | |
190 | |
191 class ArchiveJob::ArchiveIndex : public boost::noncopyable | |
192 { | |
193 private: | |
194 struct Instance | |
195 { | |
196 std::string id_; | |
197 FileInfo dicom_; | |
198 | |
199 Instance(const std::string& id, | |
200 const FileInfo& dicom) : | |
201 id_(id), dicom_(dicom) | |
202 { | |
203 } | |
204 }; | |
205 | |
206 // A "NULL" value for ArchiveIndex indicates a non-expanded node | |
207 typedef std::map<std::string, ArchiveIndex*> Resources; | |
208 | |
209 ResourceType level_; | |
210 Resources resources_; // Only at patient/study/series level | |
211 std::list<Instance> instances_; // Only at instance level | |
212 | |
213 | |
214 void AddResourceToExpand(ServerIndex& index, | |
215 const std::string& id) | |
216 { | |
217 if (level_ == ResourceType_Instance) | |
218 { | |
219 FileInfo tmp; | |
220 if (index.LookupAttachment(tmp, id, FileContentType_Dicom)) | |
221 { | |
222 instances_.push_back(Instance(id, tmp)); | |
223 } | |
224 } | |
225 else | |
226 { | |
227 resources_[id] = NULL; | |
228 } | |
229 } | |
230 | |
231 | |
232 public: | |
233 ArchiveIndex(ResourceType level) : | |
234 level_(level) | |
235 { | |
236 } | |
237 | |
238 ~ArchiveIndex() | |
239 { | |
240 for (Resources::iterator it = resources_.begin(); | |
241 it != resources_.end(); ++it) | |
242 { | |
243 delete it->second; | |
244 } | |
245 } | |
246 | |
247 | |
248 void Add(ServerIndex& index, | |
249 const ResourceIdentifiers& resource) | |
250 { | |
251 const std::string& id = resource.GetIdentifier(level_); | |
252 Resources::iterator previous = resources_.find(id); | |
253 | |
254 if (level_ == ResourceType_Instance) | |
255 { | |
256 AddResourceToExpand(index, id); | |
257 } | |
258 else if (resource.GetLevel() == level_) | |
259 { | |
260 // Mark this resource for further expansion | |
261 if (previous != resources_.end()) | |
262 { | |
263 delete previous->second; | |
264 } | |
265 | |
266 resources_[id] = NULL; | |
267 } | |
268 else if (previous == resources_.end()) | |
269 { | |
270 // This is the first time we meet this resource | |
271 std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); | |
272 child->Add(index, resource); | |
273 resources_[id] = child.release(); | |
274 } | |
275 else if (previous->second != NULL) | |
276 { | |
277 previous->second->Add(index, resource); | |
278 } | |
279 else | |
280 { | |
281 // Nothing to do: This item is marked for further expansion | |
282 } | |
283 } | |
284 | |
285 | |
286 void Expand(ServerIndex& index) | |
287 { | |
288 if (level_ == ResourceType_Instance) | |
289 { | |
290 // Expanding an instance node makes no sense | |
291 return; | |
292 } | |
293 | |
294 for (Resources::iterator it = resources_.begin(); | |
295 it != resources_.end(); ++it) | |
296 { | |
297 if (it->second == NULL) | |
298 { | |
299 // This is resource is marked for expansion | |
300 std::list<std::string> children; | |
301 index.GetChildren(children, it->first); | |
302 | |
303 std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_))); | |
304 | |
305 for (std::list<std::string>::const_iterator | |
306 it2 = children.begin(); it2 != children.end(); ++it2) | |
307 { | |
308 child->AddResourceToExpand(index, *it2); | |
309 } | |
310 | |
311 it->second = child.release(); | |
312 } | |
313 | |
314 assert(it->second != NULL); | |
315 it->second->Expand(index); | |
316 } | |
317 } | |
318 | |
319 | |
320 void Apply(IArchiveVisitor& visitor) const | |
321 { | |
322 if (level_ == ResourceType_Instance) | |
323 { | |
324 for (std::list<Instance>::const_iterator | |
325 it = instances_.begin(); it != instances_.end(); ++it) | |
326 { | |
327 visitor.AddInstance(it->id_, it->dicom_); | |
328 } | |
329 } | |
330 else | |
331 { | |
332 for (Resources::const_iterator it = resources_.begin(); | |
333 it != resources_.end(); ++it) | |
334 { | |
335 assert(it->second != NULL); // There must have been a call to "Expand()" | |
336 visitor.Open(level_, it->first); | |
337 it->second->Apply(visitor); | |
338 visitor.Close(); | |
339 } | |
340 } | |
341 } | |
342 }; | |
343 | |
344 | |
345 | |
346 class ArchiveJob::ZipCommands : public boost::noncopyable | |
347 { | |
348 private: | |
349 enum Type | |
350 { | |
351 Type_OpenDirectory, | |
352 Type_CloseDirectory, | |
353 Type_WriteInstance | |
354 }; | |
355 | |
356 class Command : public boost::noncopyable | |
357 { | |
358 private: | |
359 Type type_; | |
360 std::string filename_; | |
361 std::string instanceId_; | |
362 FileInfo info_; | |
363 | |
364 public: | |
365 explicit Command(Type type) : | |
366 type_(type) | |
367 { | |
368 assert(type_ == Type_CloseDirectory); | |
369 } | |
370 | |
371 Command(Type type, | |
372 const std::string& filename) : | |
373 type_(type), | |
374 filename_(filename) | |
375 { | |
376 assert(type_ == Type_OpenDirectory); | |
377 } | |
378 | |
379 Command(Type type, | |
380 const std::string& filename, | |
381 const std::string& instanceId, | |
382 const FileInfo& info) : | |
383 type_(type), | |
384 filename_(filename), | |
385 instanceId_(instanceId), | |
386 info_(info) | |
387 { | |
388 assert(type_ == Type_WriteInstance); | |
389 } | |
390 | |
391 void Apply(HierarchicalZipWriter& writer, | |
392 ServerContext& context, | |
393 DicomDirWriter* dicomDir, | |
394 const std::string& dicomDirFolder) const | |
395 { | |
396 switch (type_) | |
397 { | |
398 case Type_OpenDirectory: | |
399 writer.OpenDirectory(filename_.c_str()); | |
400 break; | |
401 | |
402 case Type_CloseDirectory: | |
403 writer.CloseDirectory(); | |
404 break; | |
405 | |
406 case Type_WriteInstance: | |
407 { | |
408 std::string content; | |
409 | |
410 try | |
411 { | |
412 context.ReadAttachment(content, info_); | |
413 } | |
414 catch (OrthancException& e) | |
415 { | |
416 LOG(WARNING) << "An instance was removed after the job was issued: " << instanceId_; | |
417 return; | |
418 } | |
419 | |
420 writer.OpenFile(filename_.c_str()); | |
421 writer.Write(content); | |
422 | |
423 if (dicomDir != NULL) | |
424 { | |
425 ParsedDicomFile parsed(content); | |
426 dicomDir->Add(dicomDirFolder, filename_, parsed); | |
427 } | |
428 | |
429 break; | |
430 } | |
431 | |
432 default: | |
433 throw OrthancException(ErrorCode_InternalError); | |
434 } | |
435 } | |
436 }; | |
437 | |
438 std::deque<Command*> commands_; | |
439 uint64_t uncompressedSize_; | |
440 unsigned int instancesCount_; | |
441 | |
442 | |
443 void ApplyInternal(HierarchicalZipWriter& writer, | |
444 ServerContext& context, | |
445 size_t index, | |
446 DicomDirWriter* dicomDir, | |
447 const std::string& dicomDirFolder) const | |
448 { | |
449 if (index >= commands_.size()) | |
450 { | |
451 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
452 } | |
453 | |
454 commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder); | |
455 } | |
456 | |
457 public: | |
458 ZipCommands() : | |
459 uncompressedSize_(0), | |
460 instancesCount_(0) | |
461 { | |
462 } | |
463 | |
464 ~ZipCommands() | |
465 { | |
466 for (std::deque<Command*>::iterator it = commands_.begin(); | |
467 it != commands_.end(); ++it) | |
468 { | |
469 assert(*it != NULL); | |
470 delete *it; | |
471 } | |
472 } | |
473 | |
474 size_t GetSize() const | |
475 { | |
476 return commands_.size(); | |
477 } | |
478 | |
479 unsigned int GetInstancesCount() const | |
480 { | |
481 return instancesCount_; | |
482 } | |
483 | |
484 uint64_t GetUncompressedSize() const | |
485 { | |
486 return uncompressedSize_; | |
487 } | |
488 | |
489 void Apply(HierarchicalZipWriter& writer, | |
490 ServerContext& context, | |
491 size_t index, | |
492 DicomDirWriter& dicomDir, | |
493 const std::string& dicomDirFolder) const | |
494 { | |
495 ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder); | |
496 } | |
497 | |
498 void Apply(HierarchicalZipWriter& writer, | |
499 ServerContext& context, | |
500 size_t index) const | |
501 { | |
502 ApplyInternal(writer, context, index, NULL, ""); | |
503 } | |
504 | |
505 void AddOpenDirectory(const std::string& filename) | |
506 { | |
507 commands_.push_back(new Command(Type_OpenDirectory, filename)); | |
508 } | |
509 | |
510 void AddCloseDirectory() | |
511 { | |
512 commands_.push_back(new Command(Type_CloseDirectory)); | |
513 } | |
514 | |
515 void AddWriteInstance(const std::string& filename, | |
516 const std::string& instanceId, | |
517 const FileInfo& info) | |
518 { | |
519 commands_.push_back(new Command(Type_WriteInstance, filename, instanceId, info)); | |
520 instancesCount_ ++; | |
521 uncompressedSize_ += info.GetUncompressedSize(); | |
522 } | |
523 | |
524 bool IsZip64() const | |
525 { | |
526 return IsZip64Required(GetUncompressedSize(), GetInstancesCount()); | |
527 } | |
528 }; | |
529 | |
530 | |
531 | |
532 class ArchiveJob::ArchiveIndexVisitor : public IArchiveVisitor | |
533 { | |
534 private: | |
535 ZipCommands& commands_; | |
536 ServerContext& context_; | |
537 char instanceFormat_[24]; | |
538 unsigned int counter_; | |
539 | |
540 static std::string GetTag(const DicomMap& tags, | |
541 const DicomTag& tag) | |
542 { | |
543 const DicomValue* v = tags.TestAndGetValue(tag); | |
544 if (v != NULL && | |
545 !v->IsBinary() && | |
546 !v->IsNull()) | |
547 { | |
548 return v->GetContent(); | |
549 } | |
550 else | |
551 { | |
552 return ""; | |
553 } | |
554 } | |
555 | |
556 public: | |
557 ArchiveIndexVisitor(ZipCommands& commands, | |
558 ServerContext& context) : | |
559 commands_(commands), | |
560 context_(context), | |
561 counter_(0) | |
562 { | |
563 if (commands.GetSize() != 0) | |
564 { | |
565 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
566 } | |
567 | |
568 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); | |
569 } | |
570 | |
571 virtual void Open(ResourceType level, | |
572 const std::string& publicId) | |
573 { | |
574 std::string path; | |
575 | |
576 DicomMap tags; | |
577 if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level)) | |
578 { | |
579 switch (level) | |
580 { | |
581 case ResourceType_Patient: | |
582 path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME); | |
583 break; | |
584 | |
585 case ResourceType_Study: | |
586 path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION); | |
587 break; | |
588 | |
589 case ResourceType_Series: | |
590 { | |
591 std::string modality = GetTag(tags, DICOM_TAG_MODALITY); | |
592 path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION); | |
593 | |
594 if (modality.size() == 0) | |
595 { | |
596 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); | |
597 } | |
598 else if (modality.size() == 1) | |
599 { | |
600 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", | |
601 toupper(modality[0])); | |
602 } | |
603 else if (modality.size() >= 2) | |
604 { | |
605 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", | |
606 toupper(modality[0]), toupper(modality[1])); | |
607 } | |
608 | |
609 counter_ = 0; | |
610 | |
611 break; | |
612 } | |
613 | |
614 default: | |
615 throw OrthancException(ErrorCode_InternalError); | |
616 } | |
617 } | |
618 | |
619 path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path)); | |
620 | |
621 if (path.empty()) | |
622 { | |
623 path = std::string("Unknown ") + EnumerationToString(level); | |
624 } | |
625 | |
626 commands_.AddOpenDirectory(path.c_str()); | |
627 } | |
628 | |
629 virtual void Close() | |
630 { | |
631 commands_.AddCloseDirectory(); | |
632 } | |
633 | |
634 virtual void AddInstance(const std::string& instanceId, | |
635 const FileInfo& dicom) | |
636 { | |
637 char filename[24]; | |
638 snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_); | |
639 counter_ ++; | |
640 | |
641 commands_.AddWriteInstance(filename, instanceId, dicom); | |
642 } | |
643 }; | |
644 | |
645 | |
646 class ArchiveJob::MediaIndexVisitor : public IArchiveVisitor | |
647 { | |
648 private: | |
649 ZipCommands& commands_; | |
650 ServerContext& context_; | |
651 unsigned int counter_; | |
652 | |
653 public: | |
654 MediaIndexVisitor(ZipCommands& commands, | |
655 ServerContext& context) : | |
656 commands_(commands), | |
657 context_(context), | |
658 counter_(0) | |
659 { | |
660 } | |
661 | |
662 virtual void Open(ResourceType level, | |
663 const std::string& publicId) | |
664 { | |
665 } | |
666 | |
667 virtual void Close() | |
668 { | |
669 } | |
670 | |
671 virtual void AddInstance(const std::string& instanceId, | |
672 const FileInfo& dicom) | |
673 { | |
674 // "DICOM restricts the filenames on DICOM media to 8 | |
675 // characters (some systems wrongly use 8.3, but this does not | |
676 // conform to the standard)." | |
677 std::string filename = "IM" + boost::lexical_cast<std::string>(counter_); | |
678 commands_.AddWriteInstance(filename, instanceId, dicom); | |
679 | |
680 counter_ ++; | |
681 } | |
682 }; | |
683 | |
684 | |
685 class ArchiveJob::ZipWriterIterator : public boost::noncopyable | |
686 { | |
687 private: | |
688 TemporaryFile& target_; | |
689 ServerContext& context_; | |
690 ZipCommands commands_; | |
691 std::auto_ptr<HierarchicalZipWriter> zip_; | |
692 std::auto_ptr<DicomDirWriter> dicomDir_; | |
693 bool isMedia_; | |
694 | |
695 public: | |
696 ZipWriterIterator(TemporaryFile& target, | |
697 ServerContext& context, | |
698 ArchiveIndex& archive, | |
699 bool isMedia, | |
700 bool enableExtendedSopClass) : | |
701 target_(target), | |
702 context_(context), | |
703 isMedia_(isMedia) | |
704 { | |
705 if (isMedia) | |
706 { | |
707 MediaIndexVisitor visitor(commands_, context); | |
708 archive.Expand(context.GetIndex()); | |
709 | |
710 commands_.AddOpenDirectory(MEDIA_IMAGES_FOLDER); | |
711 archive.Apply(visitor); | |
712 commands_.AddCloseDirectory(); | |
713 | |
714 dicomDir_.reset(new DicomDirWriter); | |
715 dicomDir_->EnableExtendedSopClass(enableExtendedSopClass); | |
716 } | |
717 else | |
718 { | |
719 ArchiveIndexVisitor visitor(commands_, context); | |
720 archive.Expand(context.GetIndex()); | |
721 archive.Apply(visitor); | |
722 } | |
723 | |
724 zip_.reset(new HierarchicalZipWriter(target.GetPath().c_str())); | |
725 zip_->SetZip64(commands_.IsZip64()); | |
726 } | |
727 | |
728 size_t GetStepsCount() const | |
729 { | |
730 return commands_.GetSize() + 1; | |
731 } | |
732 | |
733 void RunStep(size_t index) | |
734 { | |
735 if (index > commands_.GetSize()) | |
736 { | |
737 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
738 } | |
739 else if (index == commands_.GetSize()) | |
740 { | |
741 // Last step: Add the DICOMDIR | |
742 if (isMedia_) | |
743 { | |
744 assert(dicomDir_.get() != NULL); | |
745 std::string s; | |
746 dicomDir_->Encode(s); | |
747 | |
748 zip_->OpenFile("DICOMDIR"); | |
749 zip_->Write(s); | |
750 } | |
751 } | |
752 else | |
753 { | |
754 if (isMedia_) | |
755 { | |
756 assert(dicomDir_.get() != NULL); | |
757 commands_.Apply(*zip_, context_, index, *dicomDir_, MEDIA_IMAGES_FOLDER); | |
758 } | |
759 else | |
760 { | |
761 assert(dicomDir_.get() == NULL); | |
762 commands_.Apply(*zip_, context_, index); | |
763 } | |
764 } | |
765 } | |
766 | |
767 unsigned int GetInstancesCount() const | |
768 { | |
769 return commands_.GetInstancesCount(); | |
770 } | |
771 | |
772 uint64_t GetUncompressedSize() const | |
773 { | |
774 return commands_.GetUncompressedSize(); | |
775 } | |
776 }; | |
777 | |
778 | |
779 ArchiveJob::ArchiveJob(boost::shared_ptr<TemporaryFile>& target, | |
780 ServerContext& context, | |
781 bool isMedia, | |
782 bool enableExtendedSopClass) : | |
783 target_(target), | |
784 context_(context), | |
785 archive_(new ArchiveIndex(ResourceType_Patient)), // root | |
786 isMedia_(isMedia), | |
787 enableExtendedSopClass_(enableExtendedSopClass), | |
788 currentStep_(0), | |
789 instancesCount_(0), | |
790 uncompressedSize_(0) | |
791 { | |
792 if (target.get() == NULL) | |
793 { | |
794 throw OrthancException(ErrorCode_NullPointer); | |
795 } | |
796 } | |
797 | |
798 | |
799 void ArchiveJob::AddResource(const std::string& publicId) | |
800 { | |
801 if (writer_.get() != NULL) // Already started | |
802 { | |
803 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
804 } | |
805 | |
806 ResourceIdentifiers resource(context_.GetIndex(), publicId); | |
807 archive_->Add(context_.GetIndex(), resource); | |
808 } | |
809 | |
810 | |
811 void ArchiveJob::SignalResubmit() | |
812 { | |
813 LOG(ERROR) << "Cannot resubmit the creation of an archive"; | |
814 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
815 } | |
816 | |
817 | |
818 void ArchiveJob::Start() | |
819 { | |
820 if (writer_.get() != NULL) | |
821 { | |
822 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
823 } | |
824 | |
825 writer_.reset(new ZipWriterIterator(*target_, context_, *archive_, | |
826 isMedia_, enableExtendedSopClass_)); | |
827 | |
828 instancesCount_ = writer_->GetInstancesCount(); | |
829 uncompressedSize_ = writer_->GetUncompressedSize(); | |
830 } | |
831 | |
832 | |
833 JobStepResult ArchiveJob::ExecuteStep() | |
834 { | |
835 assert(writer_.get() != NULL); | |
836 | |
837 if (target_.unique()) | |
838 { | |
839 LOG(WARNING) << "A client has disconnected while creating an archive"; | |
840 return JobStepResult::Failure(ErrorCode_NetworkProtocol); | |
841 } | |
842 | |
843 if (writer_->GetStepsCount() == 0) | |
844 { | |
845 writer_.reset(NULL); // Flush all the results | |
846 return JobStepResult::Success(); | |
847 } | |
848 else | |
849 { | |
850 writer_->RunStep(currentStep_); | |
851 | |
852 currentStep_ ++; | |
853 | |
854 if (currentStep_ == writer_->GetStepsCount()) | |
855 { | |
856 writer_.reset(NULL); // Flush all the results | |
857 return JobStepResult::Success(); | |
858 } | |
859 else | |
860 { | |
861 return JobStepResult::Continue(); | |
862 } | |
863 } | |
864 } | |
865 | |
866 | |
867 float ArchiveJob::GetProgress() | |
868 { | |
869 if (writer_.get() == NULL || | |
870 writer_->GetStepsCount() == 0) | |
871 { | |
872 return 1; | |
873 } | |
874 else | |
875 { | |
876 return (static_cast<float>(currentStep_) / | |
877 static_cast<float>(writer_->GetStepsCount() - 1)); | |
878 } | |
879 } | |
880 | |
881 | |
882 void ArchiveJob::GetJobType(std::string& target) | |
883 { | |
884 if (isMedia_) | |
885 { | |
886 target = "Media"; | |
887 } | |
888 else | |
889 { | |
890 target = "Archive"; | |
891 } | |
892 } | |
893 | |
894 | |
895 void ArchiveJob::GetPublicContent(Json::Value& value) | |
896 { | |
897 value["Description"] = description_; | |
898 value["InstancesCount"] = instancesCount_; | |
899 value["UncompressedSizeMB"] = | |
900 static_cast<unsigned int>(uncompressedSize_ / (1024llu * 1024llu)); | |
901 } | |
902 | |
903 | |
904 void ArchiveJob::GetInternalContent(Json::Value& value) | |
905 { | |
906 // TODO | |
907 } | |
908 } |