comparison OrthancServer/ServerIndex.cpp @ 3160:fc9a4a2dad63

merge
author Alain Mazy <alain@mazy.be>
date Thu, 24 Jan 2019 10:55:19 +0100
parents 2e1711f80f74
children 8ea7c4546c3a
comparison
equal deleted inserted replaced
3159:4cfed5c2eacd 3160:fc9a4a2dad63
36 36
37 #ifndef NOMINMAX 37 #ifndef NOMINMAX
38 #define NOMINMAX 38 #define NOMINMAX
39 #endif 39 #endif
40 40
41 #include "ServerIndexChange.h" 41 #include "../Core/DicomFormat/DicomArray.h"
42 #include "../Core/DicomParsing/FromDcmtkBridge.h"
43 #include "../Core/DicomParsing/ParsedDicomFile.h"
44 #include "../Core/Logging.h"
45 #include "../Core/Toolbox.h"
46
47 #include "Database/ResourcesContent.h"
48 #include "DicomInstanceToStore.h"
42 #include "EmbeddedResources.h" 49 #include "EmbeddedResources.h"
43 #include "OrthancConfiguration.h" 50 #include "OrthancConfiguration.h"
44 #include "../Core/DicomParsing/ParsedDicomFile.h" 51 #include "Search/DatabaseLookup.h"
52 #include "Search/DicomTagConstraint.h"
53 #include "ServerContext.h"
54 #include "ServerIndexChange.h"
45 #include "ServerToolbox.h" 55 #include "ServerToolbox.h"
46 #include "../Core/Toolbox.h"
47 #include "../Core/Logging.h"
48 #include "../Core/DicomFormat/DicomArray.h"
49
50 #include "../Core/DicomParsing/FromDcmtkBridge.h"
51 #include "ServerContext.h"
52 #include "DicomInstanceToStore.h"
53 #include "Search/LookupResource.h"
54 56
55 #include <boost/lexical_cast.hpp> 57 #include <boost/lexical_cast.hpp>
56 #include <stdio.h> 58 #include <stdio.h>
57 59
58 static const uint64_t MEGA_BYTES = 1024 * 1024; 60 static const uint64_t MEGA_BYTES = 1024 * 1024;
59 61
60 namespace Orthanc 62 namespace Orthanc
61 { 63 {
64 static void CopyListToVector(std::vector<std::string>& target,
65 const std::list<std::string>& source)
66 {
67 target.resize(source.size());
68
69 size_t pos = 0;
70
71 for (std::list<std::string>::const_iterator
72 it = source.begin(); it != source.end(); ++it)
73 {
74 target[pos] = *it;
75 pos ++;
76 }
77 }
78
79
62 class ServerIndex::Listener : public IDatabaseListener 80 class ServerIndex::Listener : public IDatabaseListener
63 { 81 {
64 private: 82 private:
65 struct FileToRemove 83 struct FileToRemove
66 { 84 {
213 231
214 class ServerIndex::Transaction 232 class ServerIndex::Transaction
215 { 233 {
216 private: 234 private:
217 ServerIndex& index_; 235 ServerIndex& index_;
218 std::auto_ptr<SQLite::ITransaction> transaction_; 236 std::auto_ptr<IDatabaseWrapper::ITransaction> transaction_;
219 bool isCommitted_; 237 bool isCommitted_;
220 238
221 public: 239 public:
222 Transaction(ServerIndex& index) : 240 Transaction(ServerIndex& index) :
223 index_(index), 241 index_(index),
224 isCommitted_(false) 242 isCommitted_(false)
225 { 243 {
226 transaction_.reset(index_.db_.StartTransaction()); 244 transaction_.reset(index_.db_.StartTransaction());
227 transaction_->Begin(); 245 transaction_->Begin();
228 246
229 assert(index_.currentStorageSize_ == index_.db_.GetTotalCompressedSize());
230
231 index_.listener_->StartTransaction(); 247 index_.listener_->StartTransaction();
232 } 248 }
233 249
234 ~Transaction() 250 ~Transaction()
235 { 251 {
243 259
244 void Commit(uint64_t sizeOfAddedFiles) 260 void Commit(uint64_t sizeOfAddedFiles)
245 { 261 {
246 if (!isCommitted_) 262 if (!isCommitted_)
247 { 263 {
248 transaction_->Commit(); 264 int64_t delta = (static_cast<int64_t>(sizeOfAddedFiles) -
265 static_cast<int64_t>(index_.listener_->GetSizeOfFilesToRemove()));
266
267 transaction_->Commit(delta);
249 268
250 // We can remove the files once the SQLite transaction has 269 // We can remove the files once the SQLite transaction has
251 // been successfully committed. Some files might have to be 270 // been successfully committed. Some files might have to be
252 // deleted because of recycling. 271 // deleted because of recycling.
253 index_.listener_->CommitFilesToRemove(); 272 index_.listener_->CommitFilesToRemove();
254 273
255 index_.currentStorageSize_ += sizeOfAddedFiles;
256
257 assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove());
258 index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove();
259
260 // Send all the pending changes to the Orthanc plugins 274 // Send all the pending changes to the Orthanc plugins
261 index_.listener_->CommitChanges(); 275 index_.listener_->CommitChanges();
262 276
263 isCommitted_ = true; 277 isCommitted_ = true;
264 } 278 }
301 return publicId_; 315 return publicId_;
302 } 316 }
303 }; 317 };
304 318
305 319
320 class ServerIndex::MainDicomTagsRegistry : public boost::noncopyable
321 {
322 private:
323 class TagInfo
324 {
325 private:
326 ResourceType level_;
327 DicomTagType type_;
328
329 public:
330 TagInfo()
331 {
332 }
333
334 TagInfo(ResourceType level,
335 DicomTagType type) :
336 level_(level),
337 type_(type)
338 {
339 }
340
341 ResourceType GetLevel() const
342 {
343 return level_;
344 }
345
346 DicomTagType GetType() const
347 {
348 return type_;
349 }
350 };
351
352 typedef std::map<DicomTag, TagInfo> Registry;
353
354
355 Registry registry_;
356
357 void LoadTags(ResourceType level)
358 {
359 const DicomTag* tags = NULL;
360 size_t size;
361
362 ServerToolbox::LoadIdentifiers(tags, size, level);
363
364 for (size_t i = 0; i < size; i++)
365 {
366 if (registry_.find(tags[i]) == registry_.end())
367 {
368 registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier);
369 }
370 else
371 {
372 // These patient-level tags are copied in the study level
373 assert(level == ResourceType_Study &&
374 (tags[i] == DICOM_TAG_PATIENT_ID ||
375 tags[i] == DICOM_TAG_PATIENT_NAME ||
376 tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE));
377 }
378 }
379
380 DicomMap::LoadMainDicomTags(tags, size, level);
381
382 for (size_t i = 0; i < size; i++)
383 {
384 if (registry_.find(tags[i]) == registry_.end())
385 {
386 registry_[tags[i]] = TagInfo(level, DicomTagType_Main);
387 }
388 }
389 }
390
391 public:
392 MainDicomTagsRegistry()
393 {
394 LoadTags(ResourceType_Patient);
395 LoadTags(ResourceType_Study);
396 LoadTags(ResourceType_Series);
397 LoadTags(ResourceType_Instance);
398 }
399
400 void LookupTag(ResourceType& level,
401 DicomTagType& type,
402 const DicomTag& tag) const
403 {
404 Registry::const_iterator it = registry_.find(tag);
405
406 if (it == registry_.end())
407 {
408 // Default values
409 level = ResourceType_Instance;
410 type = DicomTagType_Generic;
411 }
412 else
413 {
414 level = it->second.GetLevel();
415 type = it->second.GetType();
416 }
417 }
418 };
419
420
306 bool ServerIndex::DeleteResource(Json::Value& target, 421 bool ServerIndex::DeleteResource(Json::Value& target,
307 const std::string& uuid, 422 const std::string& uuid,
308 ResourceType expectedType) 423 ResourceType expectedType)
309 { 424 {
310 boost::mutex::scoped_lock lock(mutex_); 425 boost::mutex::scoped_lock lock(mutex_);
385 500
386 LOG(INFO) << "Stopping the database flushing thread"; 501 LOG(INFO) << "Stopping the database flushing thread";
387 } 502 }
388 503
389 504
390 static void ComputeExpectedNumberOfInstances(IDatabaseWrapper& db, 505 static bool ComputeExpectedNumberOfInstances(int64_t& target,
391 int64_t series,
392 const DicomMap& dicomSummary) 506 const DicomMap& dicomSummary)
393 { 507 {
394 try 508 try
395 { 509 {
396 const DicomValue* value; 510 const DicomValue* value;
397 const DicomValue* value2; 511 const DicomValue* value2;
398 512
399 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL && 513 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL &&
400 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL) 514 !value->IsNull() &&
515 !value->IsBinary() &&
516 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL &&
517 !value2->IsNull() &&
518 !value2->IsBinary())
401 { 519 {
402 // Patch for series with temporal positions thanks to Will Ryder 520 // Patch for series with temporal positions thanks to Will Ryder
403 int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent()); 521 int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent());
404 int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent()); 522 int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent());
405 std::string expected = boost::lexical_cast<std::string>(imagesInAcquisition * countTemporalPositions); 523 target = imagesInAcquisition * countTemporalPositions;
406 db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); 524 return (target > 0);
407 } 525 }
408 526
409 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL && 527 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL &&
410 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL) 528 !value->IsNull() &&
529 !value->IsBinary() &&
530 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL &&
531 !value2->IsBinary() &&
532 !value2->IsNull())
411 { 533 {
412 // Support of Cardio-PET images 534 // Support of Cardio-PET images
413 int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent()); 535 int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent());
414 int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent()); 536 int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent());
415 std::string expected = boost::lexical_cast<std::string>(numberOfSlices * numberOfTimeSlices); 537 target = numberOfSlices * numberOfTimeSlices;
416 db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); 538 return (target > 0);
417 } 539 }
418 540
419 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL) 541 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL &&
420 { 542 !value->IsNull() &&
421 db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->GetContent()); 543 !value->IsBinary())
544 {
545 target = boost::lexical_cast<int64_t>(value->GetContent());
546 return (target > 0);
422 } 547 }
423 } 548 }
424 catch (OrthancException&) 549 catch (OrthancException&)
425 { 550 {
426 } 551 }
427 catch (boost::bad_lexical_cast&) 552 catch (boost::bad_lexical_cast&)
428 { 553 {
429 } 554 }
555
556 return false;
430 } 557 }
431 558
432 559
433 560
434 561
496 db_.SetGlobalProperty(property, "1"); 623 db_.SetGlobalProperty(property, "1");
497 return 1; 624 return 1;
498 } 625 }
499 } 626 }
500 627
501
502
503 int64_t ServerIndex::CreateResource(const std::string& publicId,
504 ResourceType type)
505 {
506 int64_t id = db_.CreateResource(publicId, type);
507
508 ChangeType changeType;
509 switch (type)
510 {
511 case ResourceType_Patient:
512 changeType = ChangeType_NewPatient;
513 break;
514
515 case ResourceType_Study:
516 changeType = ChangeType_NewStudy;
517 break;
518
519 case ResourceType_Series:
520 changeType = ChangeType_NewSeries;
521 break;
522
523 case ResourceType_Instance:
524 changeType = ChangeType_NewInstance;
525 break;
526
527 default:
528 throw OrthancException(ErrorCode_InternalError);
529 }
530
531 ServerIndexChange change(changeType, type, publicId);
532 db_.LogChange(id, change);
533
534 assert(listener_.get() != NULL);
535 listener_->SignalChange(change);
536
537 return id;
538 }
539 628
540 629
541 ServerIndex::ServerIndex(ServerContext& context, 630 ServerIndex::ServerIndex(ServerContext& context,
542 IDatabaseWrapper& db, 631 IDatabaseWrapper& db,
543 unsigned int threadSleep) : 632 unsigned int threadSleep) :
544 done_(false), 633 done_(false),
545 db_(db), 634 db_(db),
546 maximumStorageSize_(0), 635 maximumStorageSize_(0),
547 maximumPatients_(0), 636 maximumPatients_(0),
548 overwrite_(false) 637 overwrite_(false),
638 mainDicomTagsRegistry_(new MainDicomTagsRegistry)
549 { 639 {
550 listener_.reset(new Listener(context)); 640 listener_.reset(new Listener(context));
551 db_.SetListener(*listener_); 641 db_.SetListener(*listener_);
552
553 currentStorageSize_ = db_.GetTotalCompressedSize();
554 642
555 // Initial recycling if the parameters have changed since the last 643 // Initial recycling if the parameters have changed since the last
556 // execution of Orthanc 644 // execution of Orthanc
557 StandaloneRecycling(); 645 StandaloneRecycling();
558 646
596 } 684 }
597 } 685 }
598 } 686 }
599 687
600 688
601 689 static void SetInstanceMetadata(ResourcesContent& content,
602 void ServerIndex::SetInstanceMetadata(std::map<MetadataType, std::string>& instanceMetadata, 690 std::map<MetadataType, std::string>& instanceMetadata,
603 int64_t instance, 691 int64_t instance,
604 MetadataType metadata, 692 MetadataType metadata,
605 const std::string& value) 693 const std::string& value)
606 { 694 {
607 db_.SetMetadata(instance, metadata, value); 695 content.AddMetadata(instance, metadata, value);
608 instanceMetadata[metadata] = value; 696 instanceMetadata[metadata] = value;
609 } 697 }
610 698
611 699
612 700 void ServerIndex::SignalNewResource(ChangeType changeType,
701 ResourceType level,
702 const std::string& publicId,
703 int64_t internalId)
704 {
705 ServerIndexChange change(changeType, level, publicId);
706 db_.LogChange(internalId, change);
707
708 assert(listener_.get() != NULL);
709 listener_->SignalChange(change);
710 }
711
712
613 StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata, 713 StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
614 DicomInstanceToStore& instanceToStore, 714 DicomInstanceToStore& instanceToStore,
615 const Attachments& attachments) 715 const Attachments& attachments)
616 { 716 {
617 boost::mutex::scoped_lock lock(mutex_); 717 boost::mutex::scoped_lock lock(mutex_);
618 718
619 const DicomMap& dicomSummary = instanceToStore.GetSummary(); 719 const DicomMap& dicomSummary = instanceToStore.GetSummary();
620 const ServerIndex::MetadataMap& metadata = instanceToStore.GetMetadata(); 720 const ServerIndex::MetadataMap& metadata = instanceToStore.GetMetadata();
621 721
722 int64_t expectedInstances;
723 const bool hasExpectedInstances =
724 ComputeExpectedNumberOfInstances(expectedInstances, dicomSummary);
725
622 instanceMetadata.clear(); 726 instanceMetadata.clear();
623 727
728 const std::string hashPatient = instanceToStore.GetHasher().HashPatient();
729 const std::string hashStudy = instanceToStore.GetHasher().HashStudy();
730 const std::string hashSeries = instanceToStore.GetHasher().HashSeries();
731 const std::string hashInstance = instanceToStore.GetHasher().HashInstance();
732
624 try 733 try
625 { 734 {
626 Transaction t(*this); 735 Transaction t(*this);
627 736
737 IDatabaseWrapper::CreateInstanceResult status;
738 int64_t instanceId;
739
628 // Check whether this instance is already stored 740 // Check whether this instance is already stored
629 { 741 if (!db_.CreateInstance(status, instanceId, hashPatient,
630 ResourceType type; 742 hashStudy, hashSeries, hashInstance))
631 int64_t tmp; 743 {
632 if (db_.LookupResource(tmp, type, instanceToStore.GetHasher().HashInstance())) 744 // The instance already exists
633 { 745
634 assert(type == ResourceType_Instance); 746 if (overwrite_)
635 747 {
636 if (overwrite_) 748 // Overwrite the old instance
749 LOG(INFO) << "Overwriting instance: " << hashInstance;
750 db_.DeleteResource(instanceId);
751
752 // Re-create the instance, now that the old one is removed
753 if (!db_.CreateInstance(status, instanceId, hashPatient,
754 hashStudy, hashSeries, hashInstance))
637 { 755 {
638 // Overwrite the old instance 756 throw OrthancException(ErrorCode_InternalError);
639 LOG(INFO) << "Overwriting instance: " << instanceToStore.GetHasher().HashInstance();
640 db_.DeleteResource(tmp);
641 } 757 }
642 else 758 }
643 { 759 else
644 // Do nothing if the instance already exists 760 {
645 db_.GetAllMetadata(instanceMetadata, tmp); 761 // Do nothing if the instance already exists and overwriting is disabled
646 return StoreStatus_AlreadyStored; 762 db_.GetAllMetadata(instanceMetadata, instanceId);
647 } 763 return StoreStatus_AlreadyStored;
648 } 764 }
649 } 765 }
650 766
767
768 // Warn about the creation of new resources. The order must be
769 // from instance to patient.
770
771 // NB: In theory, could be sped up by grouping the underlying
772 // calls to "db_.LogChange()". However, this would only have an
773 // impact when new patient/study/series get created, which
774 // occurs far less often that creating new instances. The
775 // positive impact looks marginal in practice.
776 SignalNewResource(ChangeType_NewInstance, ResourceType_Instance, hashInstance, instanceId);
777
778 if (status.isNewSeries_)
779 {
780 SignalNewResource(ChangeType_NewSeries, ResourceType_Series, hashSeries, status.seriesId_);
781 }
782
783 if (status.isNewStudy_)
784 {
785 SignalNewResource(ChangeType_NewStudy, ResourceType_Study, hashStudy, status.studyId_);
786 }
787
788 if (status.isNewPatient_)
789 {
790 SignalNewResource(ChangeType_NewPatient, ResourceType_Patient, hashPatient, status.patientId_);
791 }
792
793
651 // Ensure there is enough room in the storage for the new instance 794 // Ensure there is enough room in the storage for the new instance
652 uint64_t instanceSize = 0; 795 uint64_t instanceSize = 0;
653 for (Attachments::const_iterator it = attachments.begin(); 796 for (Attachments::const_iterator it = attachments.begin();
654 it != attachments.end(); ++it) 797 it != attachments.end(); ++it)
655 { 798 {
656 instanceSize += it->GetCompressedSize(); 799 instanceSize += it->GetCompressedSize();
657 } 800 }
658 801
659 Recycle(instanceSize, instanceToStore.GetHasher().HashPatient()); 802 Recycle(instanceSize, hashPatient /* don't consider the current patient for recycling */);
660 803
661 // Create the instance 804
662 int64_t instance = CreateResource(instanceToStore.GetHasher().HashInstance(), ResourceType_Instance);
663 ServerToolbox::StoreMainDicomTags(db_, instance, ResourceType_Instance, dicomSummary);
664
665 // Detect up to which level the patient/study/series/instance
666 // hierarchy must be created
667 int64_t patient = -1, study = -1, series = -1;
668 bool isNewPatient = false;
669 bool isNewStudy = false;
670 bool isNewSeries = false;
671
672 {
673 ResourceType dummy;
674
675 if (db_.LookupResource(series, dummy, instanceToStore.GetHasher().HashSeries()))
676 {
677 assert(dummy == ResourceType_Series);
678 // The patient, the study and the series already exist
679
680 bool ok = (db_.LookupResource(patient, dummy, instanceToStore.GetHasher().HashPatient()) &&
681 db_.LookupResource(study, dummy, instanceToStore.GetHasher().HashStudy()));
682 assert(ok);
683 }
684 else if (db_.LookupResource(study, dummy, instanceToStore.GetHasher().HashStudy()))
685 {
686 assert(dummy == ResourceType_Study);
687
688 // New series: The patient and the study already exist
689 isNewSeries = true;
690
691 bool ok = db_.LookupResource(patient, dummy, instanceToStore.GetHasher().HashPatient());
692 assert(ok);
693 }
694 else if (db_.LookupResource(patient, dummy, instanceToStore.GetHasher().HashPatient()))
695 {
696 assert(dummy == ResourceType_Patient);
697
698 // New study and series: The patient already exist
699 isNewStudy = true;
700 isNewSeries = true;
701 }
702 else
703 {
704 // New patient, study and series: Nothing exists
705 isNewPatient = true;
706 isNewStudy = true;
707 isNewSeries = true;
708 }
709 }
710
711 // Create the series if needed
712 if (isNewSeries)
713 {
714 series = CreateResource(instanceToStore.GetHasher().HashSeries(), ResourceType_Series);
715 ServerToolbox::StoreMainDicomTags(db_, series, ResourceType_Series, dicomSummary);
716 }
717
718 // Create the study if needed
719 if (isNewStudy)
720 {
721 study = CreateResource(instanceToStore.GetHasher().HashStudy(), ResourceType_Study);
722 ServerToolbox::StoreMainDicomTags(db_, study, ResourceType_Study, dicomSummary);
723 }
724
725 // Create the patient if needed
726 if (isNewPatient)
727 {
728 patient = CreateResource(instanceToStore.GetHasher().HashPatient(), ResourceType_Patient);
729 ServerToolbox::StoreMainDicomTags(db_, patient, ResourceType_Patient, dicomSummary);
730 }
731
732 // Create the parent-to-child links
733 db_.AttachChild(series, instance);
734
735 if (isNewSeries)
736 {
737 db_.AttachChild(study, series);
738 }
739
740 if (isNewStudy)
741 {
742 db_.AttachChild(patient, study);
743 }
744
745 // Sanity checks
746 assert(patient != -1);
747 assert(study != -1);
748 assert(series != -1);
749 assert(instance != -1);
750
751 // Attach the files to the newly created instance 805 // Attach the files to the newly created instance
752 for (Attachments::const_iterator it = attachments.begin(); 806 for (Attachments::const_iterator it = attachments.begin();
753 it != attachments.end(); ++it) 807 it != attachments.end(); ++it)
754 { 808 {
755 db_.AddAttachment(instance, *it); 809 db_.AddAttachment(instanceId, *it);
756 } 810 }
757 811
758 // Attach the user-specified metadata 812
759 for (MetadataMap::const_iterator 813 {
760 it = metadata.begin(); it != metadata.end(); ++it) 814 ResourcesContent content;
761 { 815
762 switch (it->first.first) 816 // Populate the tags of the newly-created resources
763 { 817
764 case ResourceType_Patient: 818 content.AddResource(instanceId, ResourceType_Instance, dicomSummary);
765 db_.SetMetadata(patient, it->first.second, it->second); 819
766 break; 820 if (status.isNewSeries_)
767 821 {
768 case ResourceType_Study: 822 content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary);
769 db_.SetMetadata(study, it->first.second, it->second); 823 }
770 break; 824
771 825 if (status.isNewStudy_)
772 case ResourceType_Series: 826 {
773 db_.SetMetadata(series, it->first.second, it->second); 827 content.AddResource(status.studyId_, ResourceType_Study, dicomSummary);
774 break; 828 }
775 829
776 case ResourceType_Instance: 830 if (status.isNewPatient_)
777 SetInstanceMetadata(instanceMetadata, instance, it->first.second, it->second); 831 {
778 break; 832 content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary);
779 833 }
780 default: 834
781 throw OrthancException(ErrorCode_ParameterOutOfRange); 835
782 } 836 // Attach the user-specified metadata
783 } 837
784 838 for (MetadataMap::const_iterator
785 // Attach the auto-computed metadata for the patient/study/series levels 839 it = metadata.begin(); it != metadata.end(); ++it)
786 std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */); 840 {
787 db_.SetMetadata(series, MetadataType_LastUpdate, now); 841 switch (it->first.first)
788 db_.SetMetadata(study, MetadataType_LastUpdate, now); 842 {
789 db_.SetMetadata(patient, MetadataType_LastUpdate, now); 843 case ResourceType_Patient:
790 844 content.AddMetadata(status.patientId_, it->first.second, it->second);
791 // Attach the auto-computed metadata for the instance level, 845 break;
792 // reflecting these additions into the input metadata map 846
793 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_ReceptionDate, now); 847 case ResourceType_Study:
794 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteAet, 848 content.AddMetadata(status.studyId_, it->first.second, it->second);
795 instanceToStore.GetOrigin().GetRemoteAetC()); 849 break;
796 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_Origin, 850
797 EnumerationToString(instanceToStore.GetOrigin().GetRequestOrigin())); 851 case ResourceType_Series:
798 852 content.AddMetadata(status.seriesId_, it->first.second, it->second);
799 { 853 break;
800 std::string s; 854
801 855 case ResourceType_Instance:
802 if (instanceToStore.LookupTransferSyntax(s)) 856 SetInstanceMetadata(content, instanceMetadata, instanceId,
803 { 857 it->first.second, it->second);
804 // New in Orthanc 1.2.0 858 break;
805 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_TransferSyntax, s); 859
806 } 860 default:
807 861 throw OrthancException(ErrorCode_ParameterOutOfRange);
808 if (instanceToStore.GetOrigin().LookupRemoteIp(s)) 862 }
809 { 863 }
810 // New in Orthanc 1.4.0 864
811 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_RemoteIp, s); 865
812 } 866 // Attach the auto-computed metadata for the patient/study/series levels
813 867 std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
814 if (instanceToStore.GetOrigin().LookupCalledAet(s)) 868 content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now);
815 { 869 content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now);
816 // New in Orthanc 1.4.0 870 content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now);
817 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_CalledAet, s); 871
818 } 872 if (status.isNewSeries_ &&
819 873 hasExpectedInstances)
820 if (instanceToStore.GetOrigin().LookupHttpUsername(s)) 874 {
821 { 875 content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances,
822 // New in Orthanc 1.4.0 876 boost::lexical_cast<std::string>(expectedInstances));
823 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_HttpUsername, s); 877 }
824 } 878
825 } 879
826 880 // Attach the auto-computed metadata for the instance level,
827 const DicomValue* value; 881 // reflecting these additions into the input metadata map
828 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && 882 SetInstanceMetadata(content, instanceMetadata, instanceId,
829 !value->IsNull() && 883 MetadataType_Instance_ReceptionDate, now);
830 !value->IsBinary()) 884 SetInstanceMetadata(content, instanceMetadata, instanceId, MetadataType_Instance_RemoteAet,
831 { 885 instanceToStore.GetOrigin().GetRemoteAetC());
832 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_SopClassUid, value->GetContent()); 886 SetInstanceMetadata(content, instanceMetadata, instanceId, MetadataType_Instance_Origin,
833 } 887 EnumerationToString(instanceToStore.GetOrigin().GetRequestOrigin()));
834 888
835 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || 889
836 (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) 890 {
837 { 891 std::string s;
838 if (!value->IsNull() && 892
893 if (instanceToStore.LookupTransferSyntax(s))
894 {
895 // New in Orthanc 1.2.0
896 SetInstanceMetadata(content, instanceMetadata, instanceId,
897 MetadataType_Instance_TransferSyntax, s);
898 }
899
900 if (instanceToStore.GetOrigin().LookupRemoteIp(s))
901 {
902 // New in Orthanc 1.4.0
903 SetInstanceMetadata(content, instanceMetadata, instanceId,
904 MetadataType_Instance_RemoteIp, s);
905 }
906
907 if (instanceToStore.GetOrigin().LookupCalledAet(s))
908 {
909 // New in Orthanc 1.4.0
910 SetInstanceMetadata(content, instanceMetadata, instanceId,
911 MetadataType_Instance_CalledAet, s);
912 }
913
914 if (instanceToStore.GetOrigin().LookupHttpUsername(s))
915 {
916 // New in Orthanc 1.4.0
917 SetInstanceMetadata(content, instanceMetadata, instanceId,
918 MetadataType_Instance_HttpUsername, s);
919 }
920 }
921
922
923 const DicomValue* value;
924 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
925 !value->IsNull() &&
839 !value->IsBinary()) 926 !value->IsBinary())
840 { 927 {
841 SetInstanceMetadata(instanceMetadata, instance, MetadataType_Instance_IndexInSeries, value->GetContent()); 928 SetInstanceMetadata(content, instanceMetadata, instanceId,
842 } 929 MetadataType_Instance_SopClassUid, value->GetContent());
843 } 930 }
844 931
932
933 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
934 (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
935 {
936 if (!value->IsNull() &&
937 !value->IsBinary())
938 {
939 SetInstanceMetadata(content, instanceMetadata, instanceId,
940 MetadataType_Instance_IndexInSeries, value->GetContent());
941 }
942 }
943
944
945 db_.SetResourcesContent(content);
946 }
947
948
845 // Check whether the series of this new instance is now completed 949 // Check whether the series of this new instance is now completed
846 if (isNewSeries) 950 SeriesStatus seriesStatus = GetSeriesStatus(status.seriesId_);
847 {
848 ComputeExpectedNumberOfInstances(db_, series, dicomSummary);
849 }
850
851 SeriesStatus seriesStatus = GetSeriesStatus(series);
852 if (seriesStatus == SeriesStatus_Complete) 951 if (seriesStatus == SeriesStatus_Complete)
853 { 952 {
854 LogChange(series, ChangeType_CompletedSeries, ResourceType_Series, instanceToStore.GetHasher().HashSeries()); 953 LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries);
855 } 954 }
955
856 956
857 // Mark the parent resources of this instance as unstable 957 // Mark the parent resources of this instance as unstable
858 MarkAsUnstable(series, ResourceType_Series, instanceToStore.GetHasher().HashSeries()); 958 MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries);
859 MarkAsUnstable(study, ResourceType_Study, instanceToStore.GetHasher().HashStudy()); 959 MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy);
860 MarkAsUnstable(patient, ResourceType_Patient, instanceToStore.GetHasher().HashPatient()); 960 MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient);
861 961
862 t.Commit(instanceSize); 962 t.Commit(instanceSize);
863 963
864 return StoreStatus_Success; 964 return StoreStatus_Success;
865 } 965 }
875 void ServerIndex::ComputeStatistics(Json::Value& target) 975 void ServerIndex::ComputeStatistics(Json::Value& target)
876 { 976 {
877 boost::mutex::scoped_lock lock(mutex_); 977 boost::mutex::scoped_lock lock(mutex_);
878 target = Json::objectValue; 978 target = Json::objectValue;
879 979
880 uint64_t cs = currentStorageSize_; 980 uint64_t cs = db_.GetTotalCompressedSize();
881 assert(cs == db_.GetTotalCompressedSize());
882 uint64_t us = db_.GetTotalUncompressedSize(); 981 uint64_t us = db_.GetTotalUncompressedSize();
883 target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs); 982 target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs);
884 target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us); 983 target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
885 target["TotalDiskSizeMB"] = static_cast<unsigned int>(cs / MEGA_BYTES); 984 target["TotalDiskSizeMB"] = static_cast<unsigned int>(cs / MEGA_BYTES);
886 target["TotalUncompressedSizeMB"] = static_cast<unsigned int>(us / MEGA_BYTES); 985 target["TotalUncompressedSizeMB"] = static_cast<unsigned int>(us / MEGA_BYTES);
890 target["CountSeries"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Series)); 989 target["CountSeries"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Series));
891 target["CountInstances"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Instance)); 990 target["CountInstances"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Instance));
892 } 991 }
893 992
894 993
994
995 SeriesStatus ServerIndex::GetSeriesStatus(int64_t id,
996 int64_t expectedNumberOfInstances)
997 {
998 std::list<std::string> values;
999 db_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries);
1000
1001 std::set<int64_t> instances;
1002
1003 for (std::list<std::string>::const_iterator
1004 it = values.begin(); it != values.end(); ++it)
1005 {
1006 int64_t index;
1007
1008 try
1009 {
1010 index = boost::lexical_cast<int64_t>(*it);
1011 }
1012 catch (boost::bad_lexical_cast&)
1013 {
1014 return SeriesStatus_Unknown;
1015 }
1016
1017 if (!(index > 0 && index <= expectedNumberOfInstances))
1018 {
1019 // Out-of-range instance index
1020 return SeriesStatus_Inconsistent;
1021 }
1022
1023 if (instances.find(index) != instances.end())
1024 {
1025 // Twice the same instance index
1026 return SeriesStatus_Inconsistent;
1027 }
1028
1029 instances.insert(index);
1030 }
1031
1032 if (static_cast<int64_t>(instances.size()) == expectedNumberOfInstances)
1033 {
1034 return SeriesStatus_Complete;
1035 }
1036 else
1037 {
1038 return SeriesStatus_Missing;
1039 }
1040 }
1041
895 1042
896 SeriesStatus ServerIndex::GetSeriesStatus(int64_t id) 1043 SeriesStatus ServerIndex::GetSeriesStatus(int64_t id)
897 { 1044 {
898 // Get the expected number of instances in this series (from the metadata) 1045 // Get the expected number of instances in this series (from the metadata)
899 int64_t expected; 1046 int64_t expected;
900 if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances)) 1047 if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances))
901 { 1048 {
902 return SeriesStatus_Unknown; 1049 return SeriesStatus_Unknown;
903 } 1050 }
904
905 // Loop over the instances of this series
906 std::list<int64_t> children;
907 db_.GetChildrenInternalId(children, id);
908
909 std::set<int64_t> instances;
910 for (std::list<int64_t>::const_iterator
911 it = children.begin(); it != children.end(); ++it)
912 {
913 // Get the index of this instance in the series
914 int64_t index;
915 if (!GetMetadataAsInteger(index, *it, MetadataType_Instance_IndexInSeries))
916 {
917 return SeriesStatus_Unknown;
918 }
919
920 if (!(index > 0 && index <= expected))
921 {
922 // Out-of-range instance index
923 return SeriesStatus_Inconsistent;
924 }
925
926 if (instances.find(index) != instances.end())
927 {
928 // Twice the same instance index
929 return SeriesStatus_Inconsistent;
930 }
931
932 instances.insert(index);
933 }
934
935 if (static_cast<int64_t>(instances.size()) == expected)
936 {
937 return SeriesStatus_Complete;
938 }
939 else 1051 else
940 { 1052 {
941 return SeriesStatus_Missing; 1053 return GetSeriesStatus(id, expected);
942 } 1054 }
943 } 1055 }
944 1056
945 1057
946 void ServerIndex::MainDicomTagsToJson(Json::Value& target, 1058 void ServerIndex::MainDicomTagsToJson(Json::Value& target,
967 target["MainDicomTags"] = Json::objectValue; 1079 target["MainDicomTags"] = Json::objectValue;
968 FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true); 1080 FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true);
969 } 1081 }
970 } 1082 }
971 1083
1084
972 bool ServerIndex::LookupResource(Json::Value& result, 1085 bool ServerIndex::LookupResource(Json::Value& result,
973 const std::string& publicId, 1086 const std::string& publicId,
974 ResourceType expectedType) 1087 ResourceType expectedType)
975 { 1088 {
976 result = Json::objectValue; 1089 result = Json::objectValue;
1065 result["Type"] = "Series"; 1178 result["Type"] = "Series";
1066 result["Status"] = EnumerationToString(GetSeriesStatus(id)); 1179 result["Status"] = EnumerationToString(GetSeriesStatus(id));
1067 1180
1068 int64_t i; 1181 int64_t i;
1069 if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances)) 1182 if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
1183 {
1070 result["ExpectedNumberOfInstances"] = static_cast<int>(i); 1184 result["ExpectedNumberOfInstances"] = static_cast<int>(i);
1185 }
1071 else 1186 else
1187 {
1072 result["ExpectedNumberOfInstances"] = Json::nullValue; 1188 result["ExpectedNumberOfInstances"] = Json::nullValue;
1189 }
1073 1190
1074 break; 1191 break;
1075 } 1192 }
1076 1193
1077 case ResourceType_Instance: 1194 case ResourceType_Instance:
1087 result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize()); 1204 result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
1088 result["FileUuid"] = attachment.GetUuid(); 1205 result["FileUuid"] = attachment.GetUuid();
1089 1206
1090 int64_t i; 1207 int64_t i;
1091 if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries)) 1208 if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
1209 {
1092 result["IndexInSeries"] = static_cast<int>(i); 1210 result["IndexInSeries"] = static_cast<int>(i);
1211 }
1093 else 1212 else
1213 {
1094 result["IndexInSeries"] = Json::nullValue; 1214 result["IndexInSeries"] = Json::nullValue;
1215 }
1095 1216
1096 break; 1217 break;
1097 } 1218 }
1098 1219
1099 default: 1220 default:
1185 template <typename T> 1306 template <typename T>
1186 static void FormatLog(Json::Value& target, 1307 static void FormatLog(Json::Value& target,
1187 const std::list<T>& log, 1308 const std::list<T>& log,
1188 const std::string& name, 1309 const std::string& name,
1189 bool done, 1310 bool done,
1190 int64_t since) 1311 int64_t since,
1312 bool hasLast,
1313 int64_t last)
1191 { 1314 {
1192 Json::Value items = Json::arrayValue; 1315 Json::Value items = Json::arrayValue;
1193 for (typename std::list<T>::const_iterator 1316 for (typename std::list<T>::const_iterator
1194 it = log.begin(); it != log.end(); ++it) 1317 it = log.begin(); it != log.end(); ++it)
1195 { 1318 {
1200 1323
1201 target = Json::objectValue; 1324 target = Json::objectValue;
1202 target[name] = items; 1325 target[name] = items;
1203 target["Done"] = done; 1326 target["Done"] = done;
1204 1327
1205 int64_t last = (log.empty() ? since : log.back().GetSeq()); 1328 if (!hasLast)
1329 {
1330 // Best-effort guess of the last index in the sequence
1331 if (log.empty())
1332 {
1333 last = since;
1334 }
1335 else
1336 {
1337 last = log.back().GetSeq();
1338 }
1339 }
1340
1206 target["Last"] = static_cast<int>(last); 1341 target["Last"] = static_cast<int>(last);
1207 } 1342 }
1208 1343
1209 1344
1210 void ServerIndex::GetChanges(Json::Value& target, 1345 void ServerIndex::GetChanges(Json::Value& target,
1211 int64_t since, 1346 int64_t since,
1212 unsigned int maxResults) 1347 unsigned int maxResults)
1213 { 1348 {
1214 std::list<ServerIndexChange> changes; 1349 std::list<ServerIndexChange> changes;
1215 bool done; 1350 bool done;
1351 bool hasLast = false;
1352 int64_t last = 0;
1216 1353
1217 { 1354 {
1218 boost::mutex::scoped_lock lock(mutex_); 1355 boost::mutex::scoped_lock lock(mutex_);
1219 1356
1220 // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as 1357 // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as
1221 // "GetLastChange()" involves calls to "GetPublicId()" 1358 // "GetLastChange()" involves calls to "GetPublicId()"
1222 Transaction transaction(*this); 1359 Transaction transaction(*this);
1360
1223 db_.GetChanges(changes, done, since, maxResults); 1361 db_.GetChanges(changes, done, since, maxResults);
1362 if (changes.empty())
1363 {
1364 last = db_.GetLastChangeIndex();
1365 hasLast = true;
1366 }
1367
1224 transaction.Commit(0); 1368 transaction.Commit(0);
1225 } 1369 }
1226 1370
1227 FormatLog(target, changes, "Changes", done, since); 1371 FormatLog(target, changes, "Changes", done, since, hasLast, last);
1228 } 1372 }
1229 1373
1230 1374
1231 void ServerIndex::GetLastChange(Json::Value& target) 1375 void ServerIndex::GetLastChange(Json::Value& target)
1232 { 1376 {
1233 std::list<ServerIndexChange> changes; 1377 std::list<ServerIndexChange> changes;
1378 bool hasLast = false;
1379 int64_t last = 0;
1234 1380
1235 { 1381 {
1236 boost::mutex::scoped_lock lock(mutex_); 1382 boost::mutex::scoped_lock lock(mutex_);
1237 1383
1238 // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as 1384 // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as
1239 // "GetLastChange()" involves calls to "GetPublicId()" 1385 // "GetLastChange()" involves calls to "GetPublicId()"
1240 Transaction transaction(*this); 1386 Transaction transaction(*this);
1387
1241 db_.GetLastChange(changes); 1388 db_.GetLastChange(changes);
1389 if (changes.empty())
1390 {
1391 last = db_.GetLastChangeIndex();
1392 hasLast = true;
1393 }
1394
1242 transaction.Commit(0); 1395 transaction.Commit(0);
1243 } 1396 }
1244 1397
1245 FormatLog(target, changes, "Changes", true, 0); 1398 FormatLog(target, changes, "Changes", true, 0, hasLast, last);
1246 } 1399 }
1247 1400
1248 1401
1249 void ServerIndex::LogExportedResource(const std::string& publicId, 1402 void ServerIndex::LogExportedResource(const std::string& publicId,
1250 const std::string& remoteModality) 1403 const std::string& remoteModality)
1346 { 1499 {
1347 boost::mutex::scoped_lock lock(mutex_); 1500 boost::mutex::scoped_lock lock(mutex_);
1348 db_.GetExportedResources(exported, done, since, maxResults); 1501 db_.GetExportedResources(exported, done, since, maxResults);
1349 } 1502 }
1350 1503
1351 FormatLog(target, exported, "Exports", done, since); 1504 FormatLog(target, exported, "Exports", done, since, false, -1);
1352 } 1505 }
1353 1506
1354 1507
1355 void ServerIndex::GetLastExportedResource(Json::Value& target) 1508 void ServerIndex::GetLastExportedResource(Json::Value& target)
1356 { 1509 {
1359 { 1512 {
1360 boost::mutex::scoped_lock lock(mutex_); 1513 boost::mutex::scoped_lock lock(mutex_);
1361 db_.GetLastExportedResource(exported); 1514 db_.GetLastExportedResource(exported);
1362 } 1515 }
1363 1516
1364 FormatLog(target, exported, "Exports", true, 0); 1517 FormatLog(target, exported, "Exports", true, 0, false, -1);
1365 } 1518 }
1366 1519
1367 1520
1368 bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize) 1521 bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize)
1369 { 1522 {
1370 if (maximumStorageSize_ != 0) 1523 if (maximumStorageSize_ != 0)
1371 { 1524 {
1372 uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove(); 1525 assert(maximumStorageSize_ >= instanceSize);
1373 assert(db_.GetTotalCompressedSize() == currentSize); 1526
1374 1527 if (db_.IsDiskSizeAbove(maximumStorageSize_ - instanceSize))
1375 if (currentSize + instanceSize > maximumStorageSize_)
1376 { 1528 {
1377 return true; 1529 return true;
1378 } 1530 }
1379 } 1531 }
1380 1532
2028 LogChange(id, ChangeType_NewChildInstance, type, publicId); 2180 LogChange(id, ChangeType_NewChildInstance, type, publicId);
2029 } 2181 }
2030 2182
2031 2183
2032 2184
2033 void ServerIndex::LookupIdentifierExact(std::list<std::string>& result, 2185 void ServerIndex::LookupIdentifierExact(std::vector<std::string>& result,
2034 ResourceType level, 2186 ResourceType level,
2035 const DicomTag& tag, 2187 const DicomTag& tag,
2036 const std::string& value) 2188 const std::string& value)
2037 { 2189 {
2038 assert((level == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || 2190 assert((level == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) ||
2041 (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || 2193 (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
2042 (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); 2194 (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
2043 2195
2044 result.clear(); 2196 result.clear();
2045 2197
2046 boost::mutex::scoped_lock lock(mutex_); 2198 DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true);
2047 2199
2048 LookupIdentifierQuery query(level); 2200 std::vector<DatabaseConstraint> query;
2049 query.AddConstraint(tag, IdentifierConstraintType_Equal, value); 2201 query.push_back(c.ConvertToDatabaseConstraint(level, DicomTagType_Identifier));
2050 query.Apply(result, db_); 2202
2203 std::list<std::string> tmp;
2204
2205 {
2206 boost::mutex::scoped_lock lock(mutex_);
2207 db_.ApplyLookupResources(tmp, NULL, query, level, 0);
2208 }
2209
2210 CopyListToVector(result, tmp);
2051 } 2211 }
2052 2212
2053 2213
2054 StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment, 2214 StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment,
2055 const std::string& publicId) 2215 const std::string& publicId)
2332 2492
2333 unsigned int ServerIndex::GetDatabaseVersion() 2493 unsigned int ServerIndex::GetDatabaseVersion()
2334 { 2494 {
2335 boost::mutex::scoped_lock lock(mutex_); 2495 boost::mutex::scoped_lock lock(mutex_);
2336 return db_.GetDatabaseVersion(); 2496 return db_.GetDatabaseVersion();
2337 }
2338
2339
2340 void ServerIndex::FindCandidates(std::vector<std::string>& resources,
2341 std::vector<std::string>& instances,
2342 const ::Orthanc::LookupResource& lookup)
2343 {
2344 boost::mutex::scoped_lock lock(mutex_);
2345
2346 std::list<int64_t> tmp;
2347 lookup.FindCandidates(tmp, db_);
2348
2349 resources.resize(tmp.size());
2350 instances.resize(tmp.size());
2351
2352 size_t pos = 0;
2353 for (std::list<int64_t>::const_iterator
2354 it = tmp.begin(); it != tmp.end(); ++it, pos++)
2355 {
2356 assert(db_.GetResourceType(*it) == lookup.GetLevel());
2357
2358 int64_t instance;
2359 if (!ServerToolbox::FindOneChildInstance(instance, db_, *it, lookup.GetLevel()))
2360 {
2361 throw OrthancException(ErrorCode_InternalError);
2362 }
2363
2364 resources[pos] = db_.GetPublicId(*it);
2365 instances[pos] = db_.GetPublicId(instance);
2366 }
2367 } 2497 }
2368 2498
2369 2499
2370 bool ServerIndex::LookupParent(std::string& target, 2500 bool ServerIndex::LookupParent(std::string& target,
2371 const std::string& publicId, 2501 const std::string& publicId,
2430 db_.ClearMainDicomTags(patient); 2560 db_.ClearMainDicomTags(patient);
2431 db_.ClearMainDicomTags(study); 2561 db_.ClearMainDicomTags(study);
2432 db_.ClearMainDicomTags(series); 2562 db_.ClearMainDicomTags(series);
2433 db_.ClearMainDicomTags(instance); 2563 db_.ClearMainDicomTags(instance);
2434 2564
2435 ServerToolbox::StoreMainDicomTags(db_, patient, ResourceType_Patient, summary); 2565 {
2436 ServerToolbox::StoreMainDicomTags(db_, study, ResourceType_Study, summary); 2566 ResourcesContent content;
2437 ServerToolbox::StoreMainDicomTags(db_, series, ResourceType_Series, summary); 2567 content.AddResource(patient, ResourceType_Patient, summary);
2438 ServerToolbox::StoreMainDicomTags(db_, instance, ResourceType_Instance, summary); 2568 content.AddResource(study, ResourceType_Study, summary);
2569 content.AddResource(series, ResourceType_Series, summary);
2570 content.AddResource(instance, ResourceType_Instance, summary);
2571 db_.SetResourcesContent(content);
2572 }
2439 2573
2440 { 2574 {
2441 std::string s; 2575 std::string s;
2442 if (dicom.LookupTransferSyntax(s)) 2576 if (dicom.LookupTransferSyntax(s))
2443 { 2577 {
2458 catch (OrthancException& e) 2592 catch (OrthancException& e)
2459 { 2593 {
2460 LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; 2594 LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
2461 } 2595 }
2462 } 2596 }
2597
2598
2599 void ServerIndex::NormalizeLookup(std::vector<DatabaseConstraint>& target,
2600 const DatabaseLookup& source,
2601 ResourceType queryLevel) const
2602 {
2603 assert(mainDicomTagsRegistry_.get() != NULL);
2604
2605 target.clear();
2606 target.reserve(source.GetConstraintsCount());
2607
2608 for (size_t i = 0; i < source.GetConstraintsCount(); i++)
2609 {
2610 ResourceType level;
2611 DicomTagType type;
2612
2613 mainDicomTagsRegistry_->LookupTag(level, type, source.GetConstraint(i).GetTag());
2614
2615 if (type == DicomTagType_Identifier ||
2616 type == DicomTagType_Main)
2617 {
2618 // Use the fact that patient-level tags are copied at the study level
2619 if (level == ResourceType_Patient &&
2620 queryLevel != ResourceType_Patient)
2621 {
2622 level = ResourceType_Study;
2623 }
2624
2625 target.push_back(source.GetConstraint(i).ConvertToDatabaseConstraint(level, type));
2626 }
2627 }
2628 }
2629
2630
2631 void ServerIndex::ApplyLookupResources(std::vector<std::string>& resourcesId,
2632 std::vector<std::string>* instancesId,
2633 const DatabaseLookup& lookup,
2634 ResourceType queryLevel,
2635 size_t limit)
2636 {
2637 std::vector<DatabaseConstraint> normalized;
2638 NormalizeLookup(normalized, lookup, queryLevel);
2639
2640 std::list<std::string> resourcesList, instancesList;
2641
2642 {
2643 boost::mutex::scoped_lock lock(mutex_);
2644
2645 if (instancesId == NULL)
2646 {
2647 db_.ApplyLookupResources(resourcesList, NULL, normalized, queryLevel, limit);
2648 }
2649 else
2650 {
2651 db_.ApplyLookupResources(resourcesList, &instancesList, normalized, queryLevel, limit);
2652 }
2653 }
2654
2655 CopyListToVector(resourcesId, resourcesList);
2656
2657 if (instancesId != NULL)
2658 {
2659 CopyListToVector(*instancesId, instancesList);
2660 }
2661 }
2463 } 2662 }