comparison OrthancServer/OrthancRestApi/OrthancRestArchive.cpp @ 1774:784a6b92d2f1

start of refactoring the creation of archives
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 12 Nov 2015 17:49:04 +0100
parents 76ed4cf74bb5
children 1861e410a9d7
comparison
equal deleted inserted replaced
1773:613df4362575 1774:784a6b92d2f1
242 writer.CloseDirectory(); 242 writer.CloseDirectory();
243 return true; 243 return true;
244 } 244 }
245 245
246 246
247 static bool IsZip64Required(ServerIndex& index, 247 static bool IsZip64Required(uint64_t uncompressedSize,
248 const std::string& id) 248 unsigned int countInstances)
249 { 249 {
250 static const uint64_t SAFETY_MARGIN = 64 * MEGA_BYTES;
251
250 /** 252 /**
251 * Determine whether ZIP64 is required. Original ZIP format can 253 * Determine whether ZIP64 is required. Original ZIP format can
252 * store up to 2GB of data (some implementation supporting up to 254 * store up to 2GB of data (some implementation supporting up to
253 * 4GB of data), and up to 65535 files. 255 * 4GB of data), and up to 65535 files.
254 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64 256 * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64
255 **/ 257 **/
256 258
259 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN ||
260 countInstances >= 65535);
261
262 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size "
263 << (uncompressedSize / MEGA_BYTES) << "MB using the "
264 << (isZip64 ? "ZIP64" : "ZIP32") << " file format";
265
266 return isZip64;
267 }
268
269
270 static bool IsZip64Required(ServerIndex& index,
271 const std::string& id)
272 {
257 uint64_t uncompressedSize; 273 uint64_t uncompressedSize;
258 uint64_t compressedSize; 274 uint64_t compressedSize;
259 unsigned int countStudies; 275 unsigned int countStudies;
260 unsigned int countSeries; 276 unsigned int countSeries;
261 unsigned int countInstances; 277 unsigned int countInstances;
278
262 index.GetStatistics(compressedSize, uncompressedSize, 279 index.GetStatistics(compressedSize, uncompressedSize,
263 countStudies, countSeries, countInstances, id); 280 countStudies, countSeries, countInstances, id);
264 const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES || 281
265 countInstances >= 65535); 282 return IsZip64Required(uncompressedSize, countInstances);
266
267 LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size "
268 << (uncompressedSize / MEGA_BYTES) << "MB using the "
269 << (isZip64 ? "ZIP64" : "ZIP32") << " file format";
270
271 return isZip64;
272 } 283 }
273 284
274 285
275 template <enum ResourceType resourceType> 286 template <enum ResourceType resourceType>
276 static void GetArchive(RestApiGetCall& call) 287 static void GetArchive(RestApiGetCall& call)
366 377
367 // The temporary file is automatically removed thanks to the RAII 378 // The temporary file is automatically removed thanks to the RAII
368 } 379 }
369 380
370 381
382
383
384 namespace
385 {
386 class Resource
387 {
388 private:
389 ResourceType level_;
390 std::string patient_;
391 std::string study_;
392 std::string series_;
393 std::string instance_;
394
395 static void GoToParent(ServerIndex& index,
396 std::string& current)
397 {
398 std::string tmp;
399
400 if (index.LookupParent(tmp, current))
401 {
402 current = tmp;
403 }
404 else
405 {
406 throw OrthancException(ErrorCode_UnknownResource);
407 }
408 }
409
410
411 public:
412 Resource(ServerIndex& index,
413 const std::string& publicId)
414 {
415 if (!index.LookupResourceType(level_, publicId))
416 {
417 throw OrthancException(ErrorCode_UnknownResource);
418 }
419
420 std::string current = publicId;;
421 switch (level_) // Do not add "break" below!
422 {
423 case ResourceType_Instance:
424 instance_ = current;
425 GoToParent(index, current);
426
427 case ResourceType_Series:
428 series_ = current;
429 GoToParent(index, current);
430
431 case ResourceType_Study:
432 study_ = current;
433 GoToParent(index, current);
434
435 case ResourceType_Patient:
436 patient_ = current;
437 break;
438
439 default:
440 throw OrthancException(ErrorCode_InternalError);
441 }
442 }
443
444 ResourceType GetLevel() const
445 {
446 return level_;
447 }
448
449 const std::string& GetIdentifier(ResourceType level) const
450 {
451 // Some sanity check to ensure enumerations are not altered
452 assert(ResourceType_Patient < ResourceType_Study);
453 assert(ResourceType_Study < ResourceType_Series);
454 assert(ResourceType_Series < ResourceType_Instance);
455
456 if (level > level_)
457 {
458 throw OrthancException(ErrorCode_InternalError);
459 }
460
461 switch (level)
462 {
463 case ResourceType_Patient:
464 return patient_;
465
466 case ResourceType_Study:
467 return study_;
468
469 case ResourceType_Series:
470 return series_;
471
472 case ResourceType_Instance:
473 return instance_;
474
475 default:
476 throw OrthancException(ErrorCode_InternalError);
477 }
478 }
479 };
480
481
482 class ArchiveIndex
483 {
484 private:
485 typedef std::map<std::string, ArchiveIndex*> Resources;
486
487 ServerIndex& index_;
488 ResourceType level_;
489 Resources resources_;
490
491 void AddResourceToExpand(const std::string& id)
492 {
493 resources_[id] = NULL;
494 }
495
496 public:
497 ArchiveIndex(ServerIndex& index,
498 ResourceType level) :
499 index_(index),
500 level_(level)
501 {
502 }
503
504 ~ArchiveIndex()
505 {
506 for (Resources::iterator it = resources_.begin();
507 it != resources_.end(); ++it)
508 {
509 delete it->second;
510 }
511 }
512
513 void Add(const Resource& resource)
514 {
515 const std::string& id = resource.GetIdentifier(level_);
516 Resources::iterator previous = resources_.find(id);
517
518 if (resource.GetLevel() == level_)
519 {
520 // Mark this resource for further expansion
521 if (previous != resources_.end())
522 {
523 delete previous->second;
524 }
525
526 resources_[id] = NULL;
527 }
528 else if (previous == resources_.end())
529 {
530 // This is the first time we meet this resource
531 std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(index_, GetChildResourceType(level_)));
532 child->Add(resource);
533 resources_[id] = child.release();
534 }
535 else if (previous->second != NULL)
536 {
537 previous->second->Add(resource);
538 }
539 else
540 {
541 // Nothing to do: This item is marked for further expansion
542 }
543 }
544
545
546 void Expand()
547 {
548 if (level_ == ResourceType_Instance)
549 {
550 return;
551 }
552
553 for (Resources::iterator it = resources_.begin();
554 it != resources_.end(); ++it)
555 {
556 if (it->second == NULL)
557 {
558 std::list<std::string> children;
559 index_.GetChildren(children, it->first);
560
561 std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(index_, GetChildResourceType(level_)));
562
563 for (std::list<std::string>::const_iterator
564 it2 = children.begin(); it2 != children.end(); ++it2)
565 {
566 child->AddResourceToExpand(*it2);
567 }
568
569 it->second = child.release();
570 }
571
572 assert(it->second != NULL);
573 it->second->Expand();
574 }
575 }
576
577
578 void ComputeStatistics(uint64_t& totalSize,
579 unsigned int& totalInstances)
580 {
581 for (Resources::iterator it = resources_.begin();
582 it != resources_.end(); ++it)
583 {
584 if (level_ == ResourceType_Instance)
585 {
586 FileInfo dicom;
587 if (index_.LookupAttachment(dicom, it->first, FileContentType_Dicom))
588 {
589 totalSize += dicom.GetUncompressedSize();
590 totalInstances ++;
591 }
592 }
593 else
594 {
595 if (it->second == NULL)
596 {
597 // The method "Expand" was not called
598 throw OrthancException(ErrorCode_InternalError);
599 }
600
601 it->second->ComputeStatistics(totalSize, totalInstances);
602 }
603 }
604 }
605
606
607 void Print(std::ostream& o,
608 const std::string& indent = "") const
609 {
610 for (Resources::const_iterator it = resources_.begin();
611 it != resources_.end(); ++it)
612 {
613 o << indent << it->first << std::endl;
614 if (it->second == NULL)
615 {
616 if (level_ != ResourceType_Instance)
617 {
618 o << indent << " *" << std::endl;
619 }
620 }
621 else
622 {
623 it->second->Print(o, indent + " ");
624 }
625 }
626 }
627 };
628 }
629
630
631 static void CreateBatchArchive(RestApiPostCall& call)
632 {
633 ServerIndex& index = OrthancRestApi::GetIndex(call);
634
635 Json::Value resources;
636 if (call.ParseJsonRequest(resources) &&
637 resources.type() == Json::arrayValue)
638 {
639 ArchiveIndex archive(index, ResourceType_Patient); // root
640
641 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
642 {
643 if (resources[i].type() != Json::stringValue)
644 {
645 return; // Bad request
646 }
647
648 Resource resource(index, resources[i].asString());
649 archive.Add(resource);
650 }
651
652 archive.Expand();
653
654 archive.Print(std::cout);
655
656 uint64_t totalSize = 0;
657 unsigned int totalInstances = 0;
658 archive.ComputeStatistics(totalSize, totalInstances);
659
660 std::cout << totalSize << " " << totalInstances << std::endl;
661 }
662 }
663
664
665
371 void OrthancRestApi::RegisterArchive() 666 void OrthancRestApi::RegisterArchive()
372 { 667 {
373 Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>); 668 Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>);
374 Register("/studies/{id}/archive", GetArchive<ResourceType_Study>); 669 Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
375 Register("/series/{id}/archive", GetArchive<ResourceType_Series>); 670 Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
376 671
377 Register("/patients/{id}/media", GetMediaArchive); 672 Register("/patients/{id}/media", GetMediaArchive);
378 Register("/studies/{id}/media", GetMediaArchive); 673 Register("/studies/{id}/media", GetMediaArchive);
379 Register("/series/{id}/media", GetMediaArchive); 674 Register("/series/{id}/media", GetMediaArchive);
675
676 Register("/tools/archive", CreateBatchArchive);
380 } 677 }
381 } 678 }