Mercurial > hg > orthanc
annotate OrthancServer/ServerJobs/ArchiveJob.cpp @ 2955:bbfd95a0c429
cont
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 03 Dec 2018 14:59:23 +0100 |
parents | ea7aea6f6a95 |
children | 9c0b0a6d8b54 |
rev | line source |
---|---|
2632 | 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 } | |
2636 | 419 |
420 //boost::this_thread::sleep(boost::posix_time::milliseconds(300)); | |
2632 | 421 |
422 writer.OpenFile(filename_.c_str()); | |
423 writer.Write(content); | |
424 | |
425 if (dicomDir != NULL) | |
426 { | |
427 ParsedDicomFile parsed(content); | |
428 dicomDir->Add(dicomDirFolder, filename_, parsed); | |
429 } | |
430 | |
431 break; | |
432 } | |
433 | |
434 default: | |
435 throw OrthancException(ErrorCode_InternalError); | |
436 } | |
437 } | |
438 }; | |
439 | |
440 std::deque<Command*> commands_; | |
441 uint64_t uncompressedSize_; | |
442 unsigned int instancesCount_; | |
443 | |
444 | |
445 void ApplyInternal(HierarchicalZipWriter& writer, | |
446 ServerContext& context, | |
447 size_t index, | |
448 DicomDirWriter* dicomDir, | |
449 const std::string& dicomDirFolder) const | |
450 { | |
451 if (index >= commands_.size()) | |
452 { | |
453 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
454 } | |
455 | |
456 commands_[index]->Apply(writer, context, dicomDir, dicomDirFolder); | |
457 } | |
458 | |
459 public: | |
460 ZipCommands() : | |
461 uncompressedSize_(0), | |
462 instancesCount_(0) | |
463 { | |
464 } | |
465 | |
466 ~ZipCommands() | |
467 { | |
468 for (std::deque<Command*>::iterator it = commands_.begin(); | |
469 it != commands_.end(); ++it) | |
470 { | |
471 assert(*it != NULL); | |
472 delete *it; | |
473 } | |
474 } | |
475 | |
476 size_t GetSize() const | |
477 { | |
478 return commands_.size(); | |
479 } | |
480 | |
481 unsigned int GetInstancesCount() const | |
482 { | |
483 return instancesCount_; | |
484 } | |
485 | |
486 uint64_t GetUncompressedSize() const | |
487 { | |
488 return uncompressedSize_; | |
489 } | |
490 | |
491 void Apply(HierarchicalZipWriter& writer, | |
492 ServerContext& context, | |
493 size_t index, | |
494 DicomDirWriter& dicomDir, | |
495 const std::string& dicomDirFolder) const | |
496 { | |
497 ApplyInternal(writer, context, index, &dicomDir, dicomDirFolder); | |
498 } | |
499 | |
500 void Apply(HierarchicalZipWriter& writer, | |
501 ServerContext& context, | |
502 size_t index) const | |
503 { | |
504 ApplyInternal(writer, context, index, NULL, ""); | |
505 } | |
506 | |
507 void AddOpenDirectory(const std::string& filename) | |
508 { | |
509 commands_.push_back(new Command(Type_OpenDirectory, filename)); | |
510 } | |
511 | |
512 void AddCloseDirectory() | |
513 { | |
514 commands_.push_back(new Command(Type_CloseDirectory)); | |
515 } | |
516 | |
517 void AddWriteInstance(const std::string& filename, | |
518 const std::string& instanceId, | |
519 const FileInfo& info) | |
520 { | |
521 commands_.push_back(new Command(Type_WriteInstance, filename, instanceId, info)); | |
522 instancesCount_ ++; | |
523 uncompressedSize_ += info.GetUncompressedSize(); | |
524 } | |
525 | |
526 bool IsZip64() const | |
527 { | |
528 return IsZip64Required(GetUncompressedSize(), GetInstancesCount()); | |
529 } | |
530 }; | |
531 | |
532 | |
533 | |
534 class ArchiveJob::ArchiveIndexVisitor : public IArchiveVisitor | |
535 { | |
536 private: | |
537 ZipCommands& commands_; | |
538 ServerContext& context_; | |
539 char instanceFormat_[24]; | |
540 unsigned int counter_; | |
541 | |
542 static std::string GetTag(const DicomMap& tags, | |
543 const DicomTag& tag) | |
544 { | |
545 const DicomValue* v = tags.TestAndGetValue(tag); | |
546 if (v != NULL && | |
547 !v->IsBinary() && | |
548 !v->IsNull()) | |
549 { | |
550 return v->GetContent(); | |
551 } | |
552 else | |
553 { | |
554 return ""; | |
555 } | |
556 } | |
557 | |
558 public: | |
559 ArchiveIndexVisitor(ZipCommands& commands, | |
560 ServerContext& context) : | |
561 commands_(commands), | |
562 context_(context), | |
563 counter_(0) | |
564 { | |
565 if (commands.GetSize() != 0) | |
566 { | |
567 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
568 } | |
569 | |
570 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); | |
571 } | |
572 | |
573 virtual void Open(ResourceType level, | |
574 const std::string& publicId) | |
575 { | |
576 std::string path; | |
577 | |
578 DicomMap tags; | |
579 if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level)) | |
580 { | |
581 switch (level) | |
582 { | |
583 case ResourceType_Patient: | |
584 path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME); | |
585 break; | |
586 | |
587 case ResourceType_Study: | |
588 path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION); | |
589 break; | |
590 | |
591 case ResourceType_Series: | |
592 { | |
593 std::string modality = GetTag(tags, DICOM_TAG_MODALITY); | |
594 path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION); | |
595 | |
596 if (modality.size() == 0) | |
597 { | |
598 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm"); | |
599 } | |
600 else if (modality.size() == 1) | |
601 { | |
602 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", | |
603 toupper(modality[0])); | |
604 } | |
605 else if (modality.size() >= 2) | |
606 { | |
607 snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", | |
608 toupper(modality[0]), toupper(modality[1])); | |
609 } | |
610 | |
611 counter_ = 0; | |
612 | |
613 break; | |
614 } | |
615 | |
616 default: | |
617 throw OrthancException(ErrorCode_InternalError); | |
618 } | |
619 } | |
620 | |
621 path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path)); | |
622 | |
623 if (path.empty()) | |
624 { | |
625 path = std::string("Unknown ") + EnumerationToString(level); | |
626 } | |
627 | |
628 commands_.AddOpenDirectory(path.c_str()); | |
629 } | |
630 | |
631 virtual void Close() | |
632 { | |
633 commands_.AddCloseDirectory(); | |
634 } | |
635 | |
636 virtual void AddInstance(const std::string& instanceId, | |
637 const FileInfo& dicom) | |
638 { | |
639 char filename[24]; | |
640 snprintf(filename, sizeof(filename) - 1, instanceFormat_, counter_); | |
641 counter_ ++; | |
642 | |
643 commands_.AddWriteInstance(filename, instanceId, dicom); | |
644 } | |
645 }; | |
646 | |
647 | |
648 class ArchiveJob::MediaIndexVisitor : public IArchiveVisitor | |
649 { | |
650 private: | |
651 ZipCommands& commands_; | |
652 ServerContext& context_; | |
653 unsigned int counter_; | |
654 | |
655 public: | |
656 MediaIndexVisitor(ZipCommands& commands, | |
657 ServerContext& context) : | |
658 commands_(commands), | |
659 context_(context), | |
660 counter_(0) | |
661 { | |
662 } | |
663 | |
664 virtual void Open(ResourceType level, | |
665 const std::string& publicId) | |
666 { | |
667 } | |
668 | |
669 virtual void Close() | |
670 { | |
671 } | |
672 | |
673 virtual void AddInstance(const std::string& instanceId, | |
674 const FileInfo& dicom) | |
675 { | |
676 // "DICOM restricts the filenames on DICOM media to 8 | |
677 // characters (some systems wrongly use 8.3, but this does not | |
678 // conform to the standard)." | |
679 std::string filename = "IM" + boost::lexical_cast<std::string>(counter_); | |
680 commands_.AddWriteInstance(filename, instanceId, dicom); | |
681 | |
682 counter_ ++; | |
683 } | |
684 }; | |
685 | |
686 | |
687 class ArchiveJob::ZipWriterIterator : public boost::noncopyable | |
688 { | |
689 private: | |
690 TemporaryFile& target_; | |
691 ServerContext& context_; | |
692 ZipCommands commands_; | |
693 std::auto_ptr<HierarchicalZipWriter> zip_; | |
694 std::auto_ptr<DicomDirWriter> dicomDir_; | |
695 bool isMedia_; | |
696 | |
697 public: | |
698 ZipWriterIterator(TemporaryFile& target, | |
699 ServerContext& context, | |
700 ArchiveIndex& archive, | |
701 bool isMedia, | |
702 bool enableExtendedSopClass) : | |
703 target_(target), | |
704 context_(context), | |
705 isMedia_(isMedia) | |
706 { | |
707 if (isMedia) | |
708 { | |
709 MediaIndexVisitor visitor(commands_, context); | |
710 archive.Expand(context.GetIndex()); | |
711 | |
712 commands_.AddOpenDirectory(MEDIA_IMAGES_FOLDER); | |
713 archive.Apply(visitor); | |
714 commands_.AddCloseDirectory(); | |
715 | |
716 dicomDir_.reset(new DicomDirWriter); | |
717 dicomDir_->EnableExtendedSopClass(enableExtendedSopClass); | |
718 } | |
719 else | |
720 { | |
721 ArchiveIndexVisitor visitor(commands_, context); | |
722 archive.Expand(context.GetIndex()); | |
723 archive.Apply(visitor); | |
724 } | |
725 | |
726 zip_.reset(new HierarchicalZipWriter(target.GetPath().c_str())); | |
727 zip_->SetZip64(commands_.IsZip64()); | |
728 } | |
729 | |
730 size_t GetStepsCount() const | |
731 { | |
732 return commands_.GetSize() + 1; | |
733 } | |
734 | |
735 void RunStep(size_t index) | |
736 { | |
737 if (index > commands_.GetSize()) | |
738 { | |
739 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
740 } | |
741 else if (index == commands_.GetSize()) | |
742 { | |
743 // Last step: Add the DICOMDIR | |
744 if (isMedia_) | |
745 { | |
746 assert(dicomDir_.get() != NULL); | |
747 std::string s; | |
748 dicomDir_->Encode(s); | |
749 | |
750 zip_->OpenFile("DICOMDIR"); | |
751 zip_->Write(s); | |
752 } | |
753 } | |
754 else | |
755 { | |
756 if (isMedia_) | |
757 { | |
758 assert(dicomDir_.get() != NULL); | |
759 commands_.Apply(*zip_, context_, index, *dicomDir_, MEDIA_IMAGES_FOLDER); | |
760 } | |
761 else | |
762 { | |
763 assert(dicomDir_.get() == NULL); | |
764 commands_.Apply(*zip_, context_, index); | |
765 } | |
766 } | |
767 } | |
768 | |
769 unsigned int GetInstancesCount() const | |
770 { | |
771 return commands_.GetInstancesCount(); | |
772 } | |
773 | |
774 uint64_t GetUncompressedSize() const | |
775 { | |
776 return commands_.GetUncompressedSize(); | |
777 } | |
778 }; | |
779 | |
780 | |
781 ArchiveJob::ArchiveJob(boost::shared_ptr<TemporaryFile>& target, | |
782 ServerContext& context, | |
783 bool isMedia, | |
784 bool enableExtendedSopClass) : | |
785 target_(target), | |
786 context_(context), | |
787 archive_(new ArchiveIndex(ResourceType_Patient)), // root | |
788 isMedia_(isMedia), | |
789 enableExtendedSopClass_(enableExtendedSopClass), | |
790 currentStep_(0), | |
791 instancesCount_(0), | |
792 uncompressedSize_(0) | |
793 { | |
794 if (target.get() == NULL) | |
795 { | |
796 throw OrthancException(ErrorCode_NullPointer); | |
797 } | |
798 } | |
799 | |
800 | |
801 void ArchiveJob::AddResource(const std::string& publicId) | |
802 { | |
803 if (writer_.get() != NULL) // Already started | |
804 { | |
805 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
806 } | |
807 | |
808 ResourceIdentifiers resource(context_.GetIndex(), publicId); | |
809 archive_->Add(context_.GetIndex(), resource); | |
810 } | |
811 | |
812 | |
2812
ea7aea6f6a95
improved naming of methods in IJob
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
2730
diff
changeset
|
813 void ArchiveJob::Reset() |
2632 | 814 { |
2955 | 815 throw OrthancException(ErrorCode_BadSequenceOfCalls, |
816 "Cannot resubmit the creation of an archive"); | |
2632 | 817 } |
818 | |
819 | |
820 void ArchiveJob::Start() | |
821 { | |
822 if (writer_.get() != NULL) | |
823 { | |
824 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
825 } | |
826 | |
827 writer_.reset(new ZipWriterIterator(*target_, context_, *archive_, | |
828 isMedia_, enableExtendedSopClass_)); | |
829 | |
830 instancesCount_ = writer_->GetInstancesCount(); | |
831 uncompressedSize_ = writer_->GetUncompressedSize(); | |
832 } | |
833 | |
834 | |
2812
ea7aea6f6a95
improved naming of methods in IJob
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
2730
diff
changeset
|
835 JobStepResult ArchiveJob::Step() |
2632 | 836 { |
837 assert(writer_.get() != NULL); | |
838 | |
839 if (target_.unique()) | |
840 { | |
841 LOG(WARNING) << "A client has disconnected while creating an archive"; | |
842 return JobStepResult::Failure(ErrorCode_NetworkProtocol); | |
843 } | |
844 | |
845 if (writer_->GetStepsCount() == 0) | |
846 { | |
2730 | 847 writer_.reset(); // Flush all the results |
2632 | 848 return JobStepResult::Success(); |
849 } | |
850 else | |
851 { | |
852 writer_->RunStep(currentStep_); | |
853 | |
854 currentStep_ ++; | |
855 | |
856 if (currentStep_ == writer_->GetStepsCount()) | |
857 { | |
2730 | 858 writer_.reset(); // Flush all the results |
2632 | 859 return JobStepResult::Success(); |
860 } | |
861 else | |
862 { | |
863 return JobStepResult::Continue(); | |
864 } | |
865 } | |
866 } | |
867 | |
868 | |
869 float ArchiveJob::GetProgress() | |
870 { | |
871 if (writer_.get() == NULL || | |
872 writer_->GetStepsCount() == 0) | |
873 { | |
874 return 1; | |
875 } | |
876 else | |
877 { | |
878 return (static_cast<float>(currentStep_) / | |
879 static_cast<float>(writer_->GetStepsCount() - 1)); | |
880 } | |
881 } | |
882 | |
883 | |
884 void ArchiveJob::GetJobType(std::string& target) | |
885 { | |
886 if (isMedia_) | |
887 { | |
888 target = "Media"; | |
889 } | |
890 else | |
891 { | |
892 target = "Archive"; | |
893 } | |
894 } | |
895 | |
896 | |
897 void ArchiveJob::GetPublicContent(Json::Value& value) | |
898 { | |
899 value["Description"] = description_; | |
900 value["InstancesCount"] = instancesCount_; | |
901 value["UncompressedSizeMB"] = | |
2643 | 902 static_cast<unsigned int>(uncompressedSize_ / MEGA_BYTES); |
2632 | 903 } |
904 } |