comparison OrthancServer/Sources/ServerIndex.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents OrthancServer/ServerIndex.cpp@058b5ade8acd
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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-2020 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 "ServerIndex.h"
36
37 #ifndef NOMINMAX
38 #define NOMINMAX
39 #endif
40
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"
49 #include "OrthancConfiguration.h"
50 #include "Search/DatabaseLookup.h"
51 #include "Search/DicomTagConstraint.h"
52 #include "ServerContext.h"
53 #include "ServerIndexChange.h"
54 #include "ServerToolbox.h"
55
56 #include <boost/lexical_cast.hpp>
57 #include <stdio.h>
58
59 static const uint64_t MEGA_BYTES = 1024 * 1024;
60
61 namespace Orthanc
62 {
63 static void CopyListToVector(std::vector<std::string>& target,
64 const std::list<std::string>& source)
65 {
66 target.resize(source.size());
67
68 size_t pos = 0;
69
70 for (std::list<std::string>::const_iterator
71 it = source.begin(); it != source.end(); ++it)
72 {
73 target[pos] = *it;
74 pos ++;
75 }
76 }
77
78
79 class ServerIndex::Listener : public IDatabaseListener
80 {
81 private:
82 struct FileToRemove
83 {
84 private:
85 std::string uuid_;
86 FileContentType type_;
87
88 public:
89 FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()),
90 type_(info.GetContentType())
91 {
92 }
93
94 const std::string& GetUuid() const
95 {
96 return uuid_;
97 }
98
99 FileContentType GetContentType() const
100 {
101 return type_;
102 }
103 };
104
105 ServerContext& context_;
106 bool hasRemainingLevel_;
107 ResourceType remainingType_;
108 std::string remainingPublicId_;
109 std::list<FileToRemove> pendingFilesToRemove_;
110 std::list<ServerIndexChange> pendingChanges_;
111 uint64_t sizeOfFilesToRemove_;
112 bool insideTransaction_;
113
114 void Reset()
115 {
116 sizeOfFilesToRemove_ = 0;
117 hasRemainingLevel_ = false;
118 pendingFilesToRemove_.clear();
119 pendingChanges_.clear();
120 }
121
122 public:
123 Listener(ServerContext& context) : context_(context),
124 insideTransaction_(false)
125 {
126 Reset();
127 assert(ResourceType_Patient < ResourceType_Study &&
128 ResourceType_Study < ResourceType_Series &&
129 ResourceType_Series < ResourceType_Instance);
130 }
131
132 void StartTransaction()
133 {
134 Reset();
135 insideTransaction_ = true;
136 }
137
138 void EndTransaction()
139 {
140 insideTransaction_ = false;
141 }
142
143 uint64_t GetSizeOfFilesToRemove()
144 {
145 return sizeOfFilesToRemove_;
146 }
147
148 void CommitFilesToRemove()
149 {
150 for (std::list<FileToRemove>::const_iterator
151 it = pendingFilesToRemove_.begin();
152 it != pendingFilesToRemove_.end(); ++it)
153 {
154 try
155 {
156 context_.RemoveFile(it->GetUuid(), it->GetContentType());
157 }
158 catch (OrthancException& e)
159 {
160 LOG(ERROR) << "Unable to remove an attachment from the storage area: "
161 << it->GetUuid() << " (type: " << EnumerationToString(it->GetContentType()) << ")";
162 }
163 }
164 }
165
166 void CommitChanges()
167 {
168 for (std::list<ServerIndexChange>::const_iterator
169 it = pendingChanges_.begin();
170 it != pendingChanges_.end(); ++it)
171 {
172 context_.SignalChange(*it);
173 }
174 }
175
176 virtual void SignalRemainingAncestor(ResourceType parentType,
177 const std::string& publicId)
178 {
179 VLOG(1) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")";
180
181 if (hasRemainingLevel_)
182 {
183 if (parentType < remainingType_)
184 {
185 remainingType_ = parentType;
186 remainingPublicId_ = publicId;
187 }
188 }
189 else
190 {
191 hasRemainingLevel_ = true;
192 remainingType_ = parentType;
193 remainingPublicId_ = publicId;
194 }
195 }
196
197 virtual void SignalFileDeleted(const FileInfo& info)
198 {
199 assert(Toolbox::IsUuid(info.GetUuid()));
200 pendingFilesToRemove_.push_back(FileToRemove(info));
201 sizeOfFilesToRemove_ += info.GetCompressedSize();
202 }
203
204 virtual void SignalChange(const ServerIndexChange& change)
205 {
206 VLOG(1) << "Change related to resource " << change.GetPublicId() << " of type "
207 << EnumerationToString(change.GetResourceType()) << ": "
208 << EnumerationToString(change.GetChangeType());
209
210 if (insideTransaction_)
211 {
212 pendingChanges_.push_back(change);
213 }
214 else
215 {
216 context_.SignalChange(change);
217 }
218 }
219
220 bool HasRemainingLevel() const
221 {
222 return hasRemainingLevel_;
223 }
224
225 ResourceType GetRemainingType() const
226 {
227 assert(HasRemainingLevel());
228 return remainingType_;
229 }
230
231 const std::string& GetRemainingPublicId() const
232 {
233 assert(HasRemainingLevel());
234 return remainingPublicId_;
235 }
236 };
237
238
239 class ServerIndex::Transaction
240 {
241 private:
242 ServerIndex& index_;
243 std::unique_ptr<IDatabaseWrapper::ITransaction> transaction_;
244 bool isCommitted_;
245
246 public:
247 Transaction(ServerIndex& index) :
248 index_(index),
249 isCommitted_(false)
250 {
251 transaction_.reset(index_.db_.StartTransaction());
252 transaction_->Begin();
253
254 index_.listener_->StartTransaction();
255 }
256
257 ~Transaction()
258 {
259 index_.listener_->EndTransaction();
260
261 if (!isCommitted_)
262 {
263 transaction_->Rollback();
264 }
265 }
266
267 void Commit(uint64_t sizeOfAddedFiles)
268 {
269 if (!isCommitted_)
270 {
271 int64_t delta = (static_cast<int64_t>(sizeOfAddedFiles) -
272 static_cast<int64_t>(index_.listener_->GetSizeOfFilesToRemove()));
273
274 transaction_->Commit(delta);
275
276 // We can remove the files once the SQLite transaction has
277 // been successfully committed. Some files might have to be
278 // deleted because of recycling.
279 index_.listener_->CommitFilesToRemove();
280
281 // Send all the pending changes to the Orthanc plugins
282 index_.listener_->CommitChanges();
283
284 isCommitted_ = true;
285 }
286 }
287 };
288
289
290 class ServerIndex::UnstableResourcePayload
291 {
292 private:
293 ResourceType type_;
294 std::string publicId_;
295 boost::posix_time::ptime time_;
296
297 public:
298 UnstableResourcePayload() : type_(ResourceType_Instance)
299 {
300 }
301
302 UnstableResourcePayload(Orthanc::ResourceType type,
303 const std::string& publicId) :
304 type_(type),
305 publicId_(publicId)
306 {
307 time_ = boost::posix_time::second_clock::local_time();
308 }
309
310 unsigned int GetAge() const
311 {
312 return (boost::posix_time::second_clock::local_time() - time_).total_seconds();
313 }
314
315 ResourceType GetResourceType() const
316 {
317 return type_;
318 }
319
320 const std::string& GetPublicId() const
321 {
322 return publicId_;
323 }
324 };
325
326
327 class ServerIndex::MainDicomTagsRegistry : public boost::noncopyable
328 {
329 private:
330 class TagInfo
331 {
332 private:
333 ResourceType level_;
334 DicomTagType type_;
335
336 public:
337 TagInfo()
338 {
339 }
340
341 TagInfo(ResourceType level,
342 DicomTagType type) :
343 level_(level),
344 type_(type)
345 {
346 }
347
348 ResourceType GetLevel() const
349 {
350 return level_;
351 }
352
353 DicomTagType GetType() const
354 {
355 return type_;
356 }
357 };
358
359 typedef std::map<DicomTag, TagInfo> Registry;
360
361
362 Registry registry_;
363
364 void LoadTags(ResourceType level)
365 {
366 {
367 const DicomTag* tags = NULL;
368 size_t size;
369
370 ServerToolbox::LoadIdentifiers(tags, size, level);
371
372 for (size_t i = 0; i < size; i++)
373 {
374 if (registry_.find(tags[i]) == registry_.end())
375 {
376 registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier);
377 }
378 else
379 {
380 // These patient-level tags are copied in the study level
381 assert(level == ResourceType_Study &&
382 (tags[i] == DICOM_TAG_PATIENT_ID ||
383 tags[i] == DICOM_TAG_PATIENT_NAME ||
384 tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE));
385 }
386 }
387 }
388
389 {
390 std::set<DicomTag> tags;
391 DicomMap::GetMainDicomTags(tags, level);
392
393 for (std::set<DicomTag>::const_iterator
394 tag = tags.begin(); tag != tags.end(); ++tag)
395 {
396 if (registry_.find(*tag) == registry_.end())
397 {
398 registry_[*tag] = TagInfo(level, DicomTagType_Main);
399 }
400 }
401 }
402 }
403
404 public:
405 MainDicomTagsRegistry()
406 {
407 LoadTags(ResourceType_Patient);
408 LoadTags(ResourceType_Study);
409 LoadTags(ResourceType_Series);
410 LoadTags(ResourceType_Instance);
411 }
412
413 void LookupTag(ResourceType& level,
414 DicomTagType& type,
415 const DicomTag& tag) const
416 {
417 Registry::const_iterator it = registry_.find(tag);
418
419 if (it == registry_.end())
420 {
421 // Default values
422 level = ResourceType_Instance;
423 type = DicomTagType_Generic;
424 }
425 else
426 {
427 level = it->second.GetLevel();
428 type = it->second.GetType();
429 }
430 }
431 };
432
433
434 bool ServerIndex::DeleteResource(Json::Value& target,
435 const std::string& uuid,
436 ResourceType expectedType)
437 {
438 boost::mutex::scoped_lock lock(mutex_);
439
440 Transaction t(*this);
441
442 int64_t id;
443 ResourceType type;
444 if (!db_.LookupResource(id, type, uuid) ||
445 expectedType != type)
446 {
447 return false;
448 }
449
450 db_.DeleteResource(id);
451
452 if (listener_->HasRemainingLevel())
453 {
454 ResourceType type = listener_->GetRemainingType();
455 const std::string& uuid = listener_->GetRemainingPublicId();
456
457 target["RemainingAncestor"] = Json::Value(Json::objectValue);
458 target["RemainingAncestor"]["Path"] = GetBasePath(type, uuid);
459 target["RemainingAncestor"]["Type"] = EnumerationToString(type);
460 target["RemainingAncestor"]["ID"] = uuid;
461 }
462 else
463 {
464 target["RemainingAncestor"] = Json::nullValue;
465 }
466
467 t.Commit(0);
468
469 return true;
470 }
471
472
473 void ServerIndex::FlushThread(ServerIndex* that,
474 unsigned int threadSleep)
475 {
476 // By default, wait for 10 seconds before flushing
477 unsigned int sleep = 10;
478
479 try
480 {
481 boost::mutex::scoped_lock lock(that->mutex_);
482 std::string sleepString;
483
484 if (that->db_.LookupGlobalProperty(sleepString, GlobalProperty_FlushSleep) &&
485 Toolbox::IsInteger(sleepString))
486 {
487 sleep = boost::lexical_cast<unsigned int>(sleepString);
488 }
489 }
490 catch (boost::bad_lexical_cast&)
491 {
492 }
493
494 LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")";
495
496 unsigned int count = 0;
497
498 while (!that->done_)
499 {
500 boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleep));
501 count++;
502 if (count < sleep)
503 {
504 continue;
505 }
506
507 Logging::Flush();
508
509 boost::mutex::scoped_lock lock(that->mutex_);
510
511 try
512 {
513 that->db_.FlushToDisk();
514 }
515 catch (OrthancException&)
516 {
517 LOG(ERROR) << "Cannot flush the SQLite database to the disk (is your filesystem full?)";
518 }
519
520 count = 0;
521 }
522
523 LOG(INFO) << "Stopping the database flushing thread";
524 }
525
526
527 static bool ComputeExpectedNumberOfInstances(int64_t& target,
528 const DicomMap& dicomSummary)
529 {
530 try
531 {
532 const DicomValue* value;
533 const DicomValue* value2;
534
535 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL &&
536 !value->IsNull() &&
537 !value->IsBinary() &&
538 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL &&
539 !value2->IsNull() &&
540 !value2->IsBinary())
541 {
542 // Patch for series with temporal positions thanks to Will Ryder
543 int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent());
544 int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent());
545 target = imagesInAcquisition * countTemporalPositions;
546 return (target > 0);
547 }
548
549 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL &&
550 !value->IsNull() &&
551 !value->IsBinary() &&
552 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL &&
553 !value2->IsBinary() &&
554 !value2->IsNull())
555 {
556 // Support of Cardio-PET images
557 int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent());
558 int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent());
559 target = numberOfSlices * numberOfTimeSlices;
560 return (target > 0);
561 }
562
563 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL &&
564 !value->IsNull() &&
565 !value->IsBinary())
566 {
567 target = boost::lexical_cast<int64_t>(value->GetContent());
568 return (target > 0);
569 }
570 }
571 catch (OrthancException&)
572 {
573 }
574 catch (boost::bad_lexical_cast&)
575 {
576 }
577
578 return false;
579 }
580
581
582
583
584 static bool LookupStringMetadata(std::string& result,
585 const std::map<MetadataType, std::string>& metadata,
586 MetadataType type)
587 {
588 std::map<MetadataType, std::string>::const_iterator found = metadata.find(type);
589
590 if (found == metadata.end())
591 {
592 return false;
593 }
594 else
595 {
596 result = found->second;
597 return true;
598 }
599 }
600
601
602 static bool LookupIntegerMetadata(int64_t& result,
603 const std::map<MetadataType, std::string>& metadata,
604 MetadataType type)
605 {
606 std::string s;
607 if (!LookupStringMetadata(s, metadata, type))
608 {
609 return false;
610 }
611
612 try
613 {
614 result = boost::lexical_cast<int64_t>(s);
615 return true;
616 }
617 catch (boost::bad_lexical_cast&)
618 {
619 return false;
620 }
621 }
622
623
624 void ServerIndex::LogChange(int64_t internalId,
625 ChangeType changeType,
626 ResourceType resourceType,
627 const std::string& publicId)
628 {
629 ServerIndexChange change(changeType, resourceType, publicId);
630
631 if (changeType <= ChangeType_INTERNAL_LastLogged)
632 {
633 db_.LogChange(internalId, change);
634 }
635
636 assert(listener_.get() != NULL);
637 listener_->SignalChange(change);
638 }
639
640
641 uint64_t ServerIndex::IncrementGlobalSequenceInternal(GlobalProperty property)
642 {
643 std::string oldValue;
644
645 if (db_.LookupGlobalProperty(oldValue, property))
646 {
647 uint64_t oldNumber;
648
649 try
650 {
651 oldNumber = boost::lexical_cast<uint64_t>(oldValue);
652 db_.SetGlobalProperty(property, boost::lexical_cast<std::string>(oldNumber + 1));
653 return oldNumber + 1;
654 }
655 catch (boost::bad_lexical_cast&)
656 {
657 throw OrthancException(ErrorCode_InternalError);
658 }
659 }
660 else
661 {
662 // Initialize the sequence at "1"
663 db_.SetGlobalProperty(property, "1");
664 return 1;
665 }
666 }
667
668
669
670 ServerIndex::ServerIndex(ServerContext& context,
671 IDatabaseWrapper& db,
672 unsigned int threadSleep) :
673 done_(false),
674 db_(db),
675 maximumStorageSize_(0),
676 maximumPatients_(0),
677 mainDicomTagsRegistry_(new MainDicomTagsRegistry)
678 {
679 listener_.reset(new Listener(context));
680 db_.SetListener(*listener_);
681
682 // Initial recycling if the parameters have changed since the last
683 // execution of Orthanc
684 StandaloneRecycling();
685
686 if (db.HasFlushToDisk())
687 {
688 flushThread_ = boost::thread(FlushThread, this, threadSleep);
689 }
690
691 unstableResourcesMonitorThread_ = boost::thread
692 (UnstableResourcesMonitorThread, this, threadSleep);
693 }
694
695
696
697 ServerIndex::~ServerIndex()
698 {
699 if (!done_)
700 {
701 LOG(ERROR) << "INTERNAL ERROR: ServerIndex::Stop() should be invoked manually to avoid mess in the destruction order!";
702 Stop();
703 }
704 }
705
706
707
708 void ServerIndex::Stop()
709 {
710 if (!done_)
711 {
712 done_ = true;
713
714 if (db_.HasFlushToDisk() &&
715 flushThread_.joinable())
716 {
717 flushThread_.join();
718 }
719
720 if (unstableResourcesMonitorThread_.joinable())
721 {
722 unstableResourcesMonitorThread_.join();
723 }
724 }
725 }
726
727
728 static void SetInstanceMetadata(ResourcesContent& content,
729 std::map<MetadataType, std::string>& instanceMetadata,
730 int64_t instance,
731 MetadataType metadata,
732 const std::string& value)
733 {
734 content.AddMetadata(instance, metadata, value);
735 instanceMetadata[metadata] = value;
736 }
737
738
739 void ServerIndex::SignalNewResource(ChangeType changeType,
740 ResourceType level,
741 const std::string& publicId,
742 int64_t internalId)
743 {
744 ServerIndexChange change(changeType, level, publicId);
745 db_.LogChange(internalId, change);
746
747 assert(listener_.get() != NULL);
748 listener_->SignalChange(change);
749 }
750
751
752 StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
753 DicomInstanceToStore& instanceToStore,
754 const Attachments& attachments,
755 bool overwrite)
756 {
757 boost::mutex::scoped_lock lock(mutex_);
758
759 const DicomMap& dicomSummary = instanceToStore.GetSummary();
760 const ServerIndex::MetadataMap& metadata = instanceToStore.GetMetadata();
761
762 int64_t expectedInstances;
763 const bool hasExpectedInstances =
764 ComputeExpectedNumberOfInstances(expectedInstances, dicomSummary);
765
766 instanceMetadata.clear();
767
768 const std::string hashPatient = instanceToStore.GetHasher().HashPatient();
769 const std::string hashStudy = instanceToStore.GetHasher().HashStudy();
770 const std::string hashSeries = instanceToStore.GetHasher().HashSeries();
771 const std::string hashInstance = instanceToStore.GetHasher().HashInstance();
772
773 try
774 {
775 Transaction t(*this);
776
777 IDatabaseWrapper::CreateInstanceResult status;
778 int64_t instanceId;
779
780 // Check whether this instance is already stored
781 if (!db_.CreateInstance(status, instanceId, hashPatient,
782 hashStudy, hashSeries, hashInstance))
783 {
784 // The instance already exists
785
786 if (overwrite)
787 {
788 // Overwrite the old instance
789 LOG(INFO) << "Overwriting instance: " << hashInstance;
790 db_.DeleteResource(instanceId);
791
792 // Re-create the instance, now that the old one is removed
793 if (!db_.CreateInstance(status, instanceId, hashPatient,
794 hashStudy, hashSeries, hashInstance))
795 {
796 throw OrthancException(ErrorCode_InternalError);
797 }
798 }
799 else
800 {
801 // Do nothing if the instance already exists and overwriting is disabled
802 db_.GetAllMetadata(instanceMetadata, instanceId);
803 return StoreStatus_AlreadyStored;
804 }
805 }
806
807
808 // Warn about the creation of new resources. The order must be
809 // from instance to patient.
810
811 // NB: In theory, could be sped up by grouping the underlying
812 // calls to "db_.LogChange()". However, this would only have an
813 // impact when new patient/study/series get created, which
814 // occurs far less often that creating new instances. The
815 // positive impact looks marginal in practice.
816 SignalNewResource(ChangeType_NewInstance, ResourceType_Instance, hashInstance, instanceId);
817
818 if (status.isNewSeries_)
819 {
820 SignalNewResource(ChangeType_NewSeries, ResourceType_Series, hashSeries, status.seriesId_);
821 }
822
823 if (status.isNewStudy_)
824 {
825 SignalNewResource(ChangeType_NewStudy, ResourceType_Study, hashStudy, status.studyId_);
826 }
827
828 if (status.isNewPatient_)
829 {
830 SignalNewResource(ChangeType_NewPatient, ResourceType_Patient, hashPatient, status.patientId_);
831 }
832
833
834 // Ensure there is enough room in the storage for the new instance
835 uint64_t instanceSize = 0;
836 for (Attachments::const_iterator it = attachments.begin();
837 it != attachments.end(); ++it)
838 {
839 instanceSize += it->GetCompressedSize();
840 }
841
842 Recycle(instanceSize, hashPatient /* don't consider the current patient for recycling */);
843
844
845 // Attach the files to the newly created instance
846 for (Attachments::const_iterator it = attachments.begin();
847 it != attachments.end(); ++it)
848 {
849 db_.AddAttachment(instanceId, *it);
850 }
851
852
853 {
854 ResourcesContent content;
855
856 // Populate the tags of the newly-created resources
857
858 content.AddResource(instanceId, ResourceType_Instance, dicomSummary);
859
860 if (status.isNewSeries_)
861 {
862 content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary);
863 }
864
865 if (status.isNewStudy_)
866 {
867 content.AddResource(status.studyId_, ResourceType_Study, dicomSummary);
868 }
869
870 if (status.isNewPatient_)
871 {
872 content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary);
873 }
874
875
876 // Attach the user-specified metadata
877
878 for (MetadataMap::const_iterator
879 it = metadata.begin(); it != metadata.end(); ++it)
880 {
881 switch (it->first.first)
882 {
883 case ResourceType_Patient:
884 content.AddMetadata(status.patientId_, it->first.second, it->second);
885 break;
886
887 case ResourceType_Study:
888 content.AddMetadata(status.studyId_, it->first.second, it->second);
889 break;
890
891 case ResourceType_Series:
892 content.AddMetadata(status.seriesId_, it->first.second, it->second);
893 break;
894
895 case ResourceType_Instance:
896 SetInstanceMetadata(content, instanceMetadata, instanceId,
897 it->first.second, it->second);
898 break;
899
900 default:
901 throw OrthancException(ErrorCode_ParameterOutOfRange);
902 }
903 }
904
905
906 // Attach the auto-computed metadata for the patient/study/series levels
907 std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
908 content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now);
909 content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now);
910 content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now);
911
912 if (status.isNewSeries_ &&
913 hasExpectedInstances)
914 {
915 content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances,
916 boost::lexical_cast<std::string>(expectedInstances));
917 }
918
919
920 // Attach the auto-computed metadata for the instance level,
921 // reflecting these additions into the input metadata map
922 SetInstanceMetadata(content, instanceMetadata, instanceId,
923 MetadataType_Instance_ReceptionDate, now);
924 SetInstanceMetadata(content, instanceMetadata, instanceId, MetadataType_Instance_RemoteAet,
925 instanceToStore.GetOrigin().GetRemoteAetC());
926 SetInstanceMetadata(content, instanceMetadata, instanceId, MetadataType_Instance_Origin,
927 EnumerationToString(instanceToStore.GetOrigin().GetRequestOrigin()));
928
929
930 {
931 std::string s;
932
933 if (instanceToStore.LookupTransferSyntax(s))
934 {
935 // New in Orthanc 1.2.0
936 SetInstanceMetadata(content, instanceMetadata, instanceId,
937 MetadataType_Instance_TransferSyntax, s);
938 }
939
940 if (instanceToStore.GetOrigin().LookupRemoteIp(s))
941 {
942 // New in Orthanc 1.4.0
943 SetInstanceMetadata(content, instanceMetadata, instanceId,
944 MetadataType_Instance_RemoteIp, s);
945 }
946
947 if (instanceToStore.GetOrigin().LookupCalledAet(s))
948 {
949 // New in Orthanc 1.4.0
950 SetInstanceMetadata(content, instanceMetadata, instanceId,
951 MetadataType_Instance_CalledAet, s);
952 }
953
954 if (instanceToStore.GetOrigin().LookupHttpUsername(s))
955 {
956 // New in Orthanc 1.4.0
957 SetInstanceMetadata(content, instanceMetadata, instanceId,
958 MetadataType_Instance_HttpUsername, s);
959 }
960 }
961
962
963 const DicomValue* value;
964 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
965 !value->IsNull() &&
966 !value->IsBinary())
967 {
968 SetInstanceMetadata(content, instanceMetadata, instanceId,
969 MetadataType_Instance_SopClassUid, value->GetContent());
970 }
971
972
973 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
974 (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
975 {
976 if (!value->IsNull() &&
977 !value->IsBinary())
978 {
979 SetInstanceMetadata(content, instanceMetadata, instanceId,
980 MetadataType_Instance_IndexInSeries, value->GetContent());
981 }
982 }
983
984
985 db_.SetResourcesContent(content);
986 }
987
988
989 // Check whether the series of this new instance is now completed
990 int64_t expectedNumberOfInstances;
991 if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary))
992 {
993 SeriesStatus seriesStatus = GetSeriesStatus(status.seriesId_, expectedNumberOfInstances);
994 if (seriesStatus == SeriesStatus_Complete)
995 {
996 LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries);
997 }
998 }
999
1000
1001 // Mark the parent resources of this instance as unstable
1002 MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries);
1003 MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy);
1004 MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient);
1005
1006 t.Commit(instanceSize);
1007
1008 return StoreStatus_Success;
1009 }
1010 catch (OrthancException& e)
1011 {
1012 LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
1013 }
1014
1015 return StoreStatus_Failure;
1016 }
1017
1018
1019 void ServerIndex::GetGlobalStatistics(/* out */ uint64_t& diskSize,
1020 /* out */ uint64_t& uncompressedSize,
1021 /* out */ uint64_t& countPatients,
1022 /* out */ uint64_t& countStudies,
1023 /* out */ uint64_t& countSeries,
1024 /* out */ uint64_t& countInstances)
1025 {
1026 boost::mutex::scoped_lock lock(mutex_);
1027 diskSize = db_.GetTotalCompressedSize();
1028 uncompressedSize = db_.GetTotalUncompressedSize();
1029 countPatients = db_.GetResourceCount(ResourceType_Patient);
1030 countStudies = db_.GetResourceCount(ResourceType_Study);
1031 countSeries = db_.GetResourceCount(ResourceType_Series);
1032 countInstances = db_.GetResourceCount(ResourceType_Instance);
1033 }
1034
1035
1036 SeriesStatus ServerIndex::GetSeriesStatus(int64_t id,
1037 int64_t expectedNumberOfInstances)
1038 {
1039 std::list<std::string> values;
1040 db_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries);
1041
1042 std::set<int64_t> instances;
1043
1044 for (std::list<std::string>::const_iterator
1045 it = values.begin(); it != values.end(); ++it)
1046 {
1047 int64_t index;
1048
1049 try
1050 {
1051 index = boost::lexical_cast<int64_t>(*it);
1052 }
1053 catch (boost::bad_lexical_cast&)
1054 {
1055 return SeriesStatus_Unknown;
1056 }
1057
1058 if (!(index > 0 && index <= expectedNumberOfInstances))
1059 {
1060 // Out-of-range instance index
1061 return SeriesStatus_Inconsistent;
1062 }
1063
1064 if (instances.find(index) != instances.end())
1065 {
1066 // Twice the same instance index
1067 return SeriesStatus_Inconsistent;
1068 }
1069
1070 instances.insert(index);
1071 }
1072
1073 if (static_cast<int64_t>(instances.size()) == expectedNumberOfInstances)
1074 {
1075 return SeriesStatus_Complete;
1076 }
1077 else
1078 {
1079 return SeriesStatus_Missing;
1080 }
1081 }
1082
1083
1084 void ServerIndex::MainDicomTagsToJson(Json::Value& target,
1085 int64_t resourceId,
1086 ResourceType resourceType)
1087 {
1088 DicomMap tags;
1089 db_.GetMainDicomTags(tags, resourceId);
1090
1091 if (resourceType == ResourceType_Study)
1092 {
1093 DicomMap t1, t2;
1094 tags.ExtractStudyInformation(t1);
1095 tags.ExtractPatientInformation(t2);
1096
1097 target["MainDicomTags"] = Json::objectValue;
1098 FromDcmtkBridge::ToJson(target["MainDicomTags"], t1, true);
1099
1100 target["PatientMainDicomTags"] = Json::objectValue;
1101 FromDcmtkBridge::ToJson(target["PatientMainDicomTags"], t2, true);
1102 }
1103 else
1104 {
1105 target["MainDicomTags"] = Json::objectValue;
1106 FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true);
1107 }
1108 }
1109
1110
1111 bool ServerIndex::LookupResource(Json::Value& result,
1112 const std::string& publicId,
1113 ResourceType expectedType)
1114 {
1115 result = Json::objectValue;
1116
1117 boost::mutex::scoped_lock lock(mutex_);
1118
1119 // Lookup for the requested resource
1120 int64_t id;
1121 ResourceType type;
1122 std::string parent;
1123 if (!db_.LookupResourceAndParent(id, type, parent, publicId) ||
1124 type != expectedType)
1125 {
1126 return false;
1127 }
1128
1129 // Set information about the parent resource (if it exists)
1130 if (type == ResourceType_Patient)
1131 {
1132 if (!parent.empty())
1133 {
1134 throw OrthancException(ErrorCode_DatabasePlugin);
1135 }
1136 }
1137 else
1138 {
1139 if (parent.empty())
1140 {
1141 throw OrthancException(ErrorCode_DatabasePlugin);
1142 }
1143
1144 switch (type)
1145 {
1146 case ResourceType_Study:
1147 result["ParentPatient"] = parent;
1148 break;
1149
1150 case ResourceType_Series:
1151 result["ParentStudy"] = parent;
1152 break;
1153
1154 case ResourceType_Instance:
1155 result["ParentSeries"] = parent;
1156 break;
1157
1158 default:
1159 throw OrthancException(ErrorCode_InternalError);
1160 }
1161 }
1162
1163 // List the children resources
1164 std::list<std::string> children;
1165 db_.GetChildrenPublicId(children, id);
1166
1167 if (type != ResourceType_Instance)
1168 {
1169 Json::Value c = Json::arrayValue;
1170
1171 for (std::list<std::string>::const_iterator
1172 it = children.begin(); it != children.end(); ++it)
1173 {
1174 c.append(*it);
1175 }
1176
1177 switch (type)
1178 {
1179 case ResourceType_Patient:
1180 result["Studies"] = c;
1181 break;
1182
1183 case ResourceType_Study:
1184 result["Series"] = c;
1185 break;
1186
1187 case ResourceType_Series:
1188 result["Instances"] = c;
1189 break;
1190
1191 default:
1192 throw OrthancException(ErrorCode_InternalError);
1193 }
1194 }
1195
1196 // Extract the metadata
1197 std::map<MetadataType, std::string> metadata;
1198 db_.GetAllMetadata(metadata, id);
1199
1200 // Set the resource type
1201 switch (type)
1202 {
1203 case ResourceType_Patient:
1204 result["Type"] = "Patient";
1205 break;
1206
1207 case ResourceType_Study:
1208 result["Type"] = "Study";
1209 break;
1210
1211 case ResourceType_Series:
1212 {
1213 result["Type"] = "Series";
1214
1215 int64_t i;
1216 if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances))
1217 {
1218 result["ExpectedNumberOfInstances"] = static_cast<int>(i);
1219 result["Status"] = EnumerationToString(GetSeriesStatus(id, i));
1220 }
1221 else
1222 {
1223 result["ExpectedNumberOfInstances"] = Json::nullValue;
1224 result["Status"] = EnumerationToString(SeriesStatus_Unknown);
1225 }
1226
1227 break;
1228 }
1229
1230 case ResourceType_Instance:
1231 {
1232 result["Type"] = "Instance";
1233
1234 FileInfo attachment;
1235 if (!db_.LookupAttachment(attachment, id, FileContentType_Dicom))
1236 {
1237 throw OrthancException(ErrorCode_InternalError);
1238 }
1239
1240 result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
1241 result["FileUuid"] = attachment.GetUuid();
1242
1243 int64_t i;
1244 if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries))
1245 {
1246 result["IndexInSeries"] = static_cast<int>(i);
1247 }
1248 else
1249 {
1250 result["IndexInSeries"] = Json::nullValue;
1251 }
1252
1253 break;
1254 }
1255
1256 default:
1257 throw OrthancException(ErrorCode_InternalError);
1258 }
1259
1260 // Record the remaining information
1261 result["ID"] = publicId;
1262 MainDicomTagsToJson(result, id, type);
1263
1264 std::string tmp;
1265
1266 if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom))
1267 {
1268 result["AnonymizedFrom"] = tmp;
1269 }
1270
1271 if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom))
1272 {
1273 result["ModifiedFrom"] = tmp;
1274 }
1275
1276 if (type == ResourceType_Patient ||
1277 type == ResourceType_Study ||
1278 type == ResourceType_Series)
1279 {
1280 result["IsStable"] = !unstableResources_.Contains(id);
1281
1282 if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate))
1283 {
1284 result["LastUpdate"] = tmp;
1285 }
1286 }
1287
1288 return true;
1289 }
1290
1291
1292 bool ServerIndex::LookupAttachment(FileInfo& attachment,
1293 const std::string& instanceUuid,
1294 FileContentType contentType)
1295 {
1296 boost::mutex::scoped_lock lock(mutex_);
1297
1298 int64_t id;
1299 ResourceType type;
1300 if (!db_.LookupResource(id, type, instanceUuid))
1301 {
1302 throw OrthancException(ErrorCode_UnknownResource);
1303 }
1304
1305 if (db_.LookupAttachment(attachment, id, contentType))
1306 {
1307 assert(attachment.GetContentType() == contentType);
1308 return true;
1309 }
1310 else
1311 {
1312 return false;
1313 }
1314 }
1315
1316
1317
1318 void ServerIndex::GetAllUuids(std::list<std::string>& target,
1319 ResourceType resourceType)
1320 {
1321 boost::mutex::scoped_lock lock(mutex_);
1322 db_.GetAllPublicIds(target, resourceType);
1323 }
1324
1325
1326 void ServerIndex::GetAllUuids(std::list<std::string>& target,
1327 ResourceType resourceType,
1328 size_t since,
1329 size_t limit)
1330 {
1331 if (limit == 0)
1332 {
1333 target.clear();
1334 return;
1335 }
1336
1337 boost::mutex::scoped_lock lock(mutex_);
1338 db_.GetAllPublicIds(target, resourceType, since, limit);
1339 }
1340
1341
1342 template <typename T>
1343 static void FormatLog(Json::Value& target,
1344 const std::list<T>& log,
1345 const std::string& name,
1346 bool done,
1347 int64_t since,
1348 bool hasLast,
1349 int64_t last)
1350 {
1351 Json::Value items = Json::arrayValue;
1352 for (typename std::list<T>::const_iterator
1353 it = log.begin(); it != log.end(); ++it)
1354 {
1355 Json::Value item;
1356 it->Format(item);
1357 items.append(item);
1358 }
1359
1360 target = Json::objectValue;
1361 target[name] = items;
1362 target["Done"] = done;
1363
1364 if (!hasLast)
1365 {
1366 // Best-effort guess of the last index in the sequence
1367 if (log.empty())
1368 {
1369 last = since;
1370 }
1371 else
1372 {
1373 last = log.back().GetSeq();
1374 }
1375 }
1376
1377 target["Last"] = static_cast<int>(last);
1378 }
1379
1380
1381 void ServerIndex::GetChanges(Json::Value& target,
1382 int64_t since,
1383 unsigned int maxResults)
1384 {
1385 std::list<ServerIndexChange> changes;
1386 bool done;
1387 bool hasLast = false;
1388 int64_t last = 0;
1389
1390 {
1391 boost::mutex::scoped_lock lock(mutex_);
1392
1393 // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as
1394 // "GetLastChange()" involves calls to "GetPublicId()"
1395 Transaction transaction(*this);
1396
1397 db_.GetChanges(changes, done, since, maxResults);
1398 if (changes.empty())
1399 {
1400 last = db_.GetLastChangeIndex();
1401 hasLast = true;
1402 }
1403
1404 transaction.Commit(0);
1405 }
1406
1407 FormatLog(target, changes, "Changes", done, since, hasLast, last);
1408 }
1409
1410
1411 void ServerIndex::GetLastChange(Json::Value& target)
1412 {
1413 std::list<ServerIndexChange> changes;
1414 bool hasLast = false;
1415 int64_t last = 0;
1416
1417 {
1418 boost::mutex::scoped_lock lock(mutex_);
1419
1420 // Fix wrt. Orthanc <= 1.3.2: A transaction was missing, as
1421 // "GetLastChange()" involves calls to "GetPublicId()"
1422 Transaction transaction(*this);
1423
1424 db_.GetLastChange(changes);
1425 if (changes.empty())
1426 {
1427 last = db_.GetLastChangeIndex();
1428 hasLast = true;
1429 }
1430
1431 transaction.Commit(0);
1432 }
1433
1434 FormatLog(target, changes, "Changes", true, 0, hasLast, last);
1435 }
1436
1437
1438 void ServerIndex::LogExportedResource(const std::string& publicId,
1439 const std::string& remoteModality)
1440 {
1441 boost::mutex::scoped_lock lock(mutex_);
1442 Transaction transaction(*this);
1443
1444 int64_t id;
1445 ResourceType type;
1446 if (!db_.LookupResource(id, type, publicId))
1447 {
1448 throw OrthancException(ErrorCode_InexistentItem);
1449 }
1450
1451 std::string patientId;
1452 std::string studyInstanceUid;
1453 std::string seriesInstanceUid;
1454 std::string sopInstanceUid;
1455
1456 int64_t currentId = id;
1457 ResourceType currentType = type;
1458
1459 // Iteratively go up inside the patient/study/series/instance hierarchy
1460 bool done = false;
1461 while (!done)
1462 {
1463 DicomMap map;
1464 db_.GetMainDicomTags(map, currentId);
1465
1466 switch (currentType)
1467 {
1468 case ResourceType_Patient:
1469 if (map.HasTag(DICOM_TAG_PATIENT_ID))
1470 {
1471 patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent();
1472 }
1473 done = true;
1474 break;
1475
1476 case ResourceType_Study:
1477 if (map.HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
1478 {
1479 studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent();
1480 }
1481 currentType = ResourceType_Patient;
1482 break;
1483
1484 case ResourceType_Series:
1485 if (map.HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
1486 {
1487 seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent();
1488 }
1489 currentType = ResourceType_Study;
1490 break;
1491
1492 case ResourceType_Instance:
1493 if (map.HasTag(DICOM_TAG_SOP_INSTANCE_UID))
1494 {
1495 sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent();
1496 }
1497 currentType = ResourceType_Series;
1498 break;
1499
1500 default:
1501 throw OrthancException(ErrorCode_InternalError);
1502 }
1503
1504 // If we have not reached the Patient level, find the parent of
1505 // the current resource
1506 if (!done)
1507 {
1508 bool ok = db_.LookupParent(currentId, currentId);
1509 assert(ok);
1510 }
1511 }
1512
1513 ExportedResource resource(-1,
1514 type,
1515 publicId,
1516 remoteModality,
1517 SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */),
1518 patientId,
1519 studyInstanceUid,
1520 seriesInstanceUid,
1521 sopInstanceUid);
1522
1523 db_.LogExportedResource(resource);
1524 transaction.Commit(0);
1525 }
1526
1527
1528 void ServerIndex::GetExportedResources(Json::Value& target,
1529 int64_t since,
1530 unsigned int maxResults)
1531 {
1532 std::list<ExportedResource> exported;
1533 bool done;
1534
1535 {
1536 boost::mutex::scoped_lock lock(mutex_);
1537 db_.GetExportedResources(exported, done, since, maxResults);
1538 }
1539
1540 FormatLog(target, exported, "Exports", done, since, false, -1);
1541 }
1542
1543
1544 void ServerIndex::GetLastExportedResource(Json::Value& target)
1545 {
1546 std::list<ExportedResource> exported;
1547
1548 {
1549 boost::mutex::scoped_lock lock(mutex_);
1550 db_.GetLastExportedResource(exported);
1551 }
1552
1553 FormatLog(target, exported, "Exports", true, 0, false, -1);
1554 }
1555
1556
1557 bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize)
1558 {
1559 if (maximumStorageSize_ != 0)
1560 {
1561 assert(maximumStorageSize_ >= instanceSize);
1562
1563 if (db_.IsDiskSizeAbove(maximumStorageSize_ - instanceSize))
1564 {
1565 return true;
1566 }
1567 }
1568
1569 if (maximumPatients_ != 0)
1570 {
1571 uint64_t patientCount = db_.GetResourceCount(ResourceType_Patient);
1572 if (patientCount > maximumPatients_)
1573 {
1574 return true;
1575 }
1576 }
1577
1578 return false;
1579 }
1580
1581
1582 void ServerIndex::Recycle(uint64_t instanceSize,
1583 const std::string& newPatientId)
1584 {
1585 if (!IsRecyclingNeeded(instanceSize))
1586 {
1587 return;
1588 }
1589
1590 // Check whether other DICOM instances from this patient are
1591 // already stored
1592 int64_t patientToAvoid;
1593 ResourceType type;
1594 bool hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId);
1595
1596 if (hasPatientToAvoid && type != ResourceType_Patient)
1597 {
1598 throw OrthancException(ErrorCode_InternalError);
1599 }
1600
1601 // Iteratively select patient to remove until there is enough
1602 // space in the DICOM store
1603 int64_t patientToRecycle;
1604 while (true)
1605 {
1606 // If other instances of this patient are already in the store,
1607 // we must avoid to recycle them
1608 bool ok = hasPatientToAvoid ?
1609 db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
1610 db_.SelectPatientToRecycle(patientToRecycle);
1611
1612 if (!ok)
1613 {
1614 throw OrthancException(ErrorCode_FullStorage);
1615 }
1616
1617 VLOG(1) << "Recycling one patient";
1618 db_.DeleteResource(patientToRecycle);
1619
1620 if (!IsRecyclingNeeded(instanceSize))
1621 {
1622 // OK, we're done
1623 break;
1624 }
1625 }
1626 }
1627
1628 void ServerIndex::SetMaximumPatientCount(unsigned int count)
1629 {
1630 boost::mutex::scoped_lock lock(mutex_);
1631 maximumPatients_ = count;
1632
1633 if (count == 0)
1634 {
1635 LOG(WARNING) << "No limit on the number of stored patients";
1636 }
1637 else
1638 {
1639 LOG(WARNING) << "At most " << count << " patients will be stored";
1640 }
1641
1642 StandaloneRecycling();
1643 }
1644
1645 void ServerIndex::SetMaximumStorageSize(uint64_t size)
1646 {
1647 boost::mutex::scoped_lock lock(mutex_);
1648 maximumStorageSize_ = size;
1649
1650 if (size == 0)
1651 {
1652 LOG(WARNING) << "No limit on the size of the storage area";
1653 }
1654 else
1655 {
1656 LOG(WARNING) << "At most " << (size / MEGA_BYTES) << "MB will be used for the storage area";
1657 }
1658
1659 StandaloneRecycling();
1660 }
1661
1662
1663 void ServerIndex::StandaloneRecycling()
1664 {
1665 // WARNING: No mutex here, do not include this as a public method
1666 Transaction t(*this);
1667 Recycle(0, "");
1668 t.Commit(0);
1669 }
1670
1671
1672 bool ServerIndex::IsProtectedPatient(const std::string& publicId)
1673 {
1674 boost::mutex::scoped_lock lock(mutex_);
1675
1676 // Lookup for the requested resource
1677 int64_t id;
1678 ResourceType type;
1679 if (!db_.LookupResource(id, type, publicId) ||
1680 type != ResourceType_Patient)
1681 {
1682 throw OrthancException(ErrorCode_ParameterOutOfRange);
1683 }
1684
1685 return db_.IsProtectedPatient(id);
1686 }
1687
1688
1689 void ServerIndex::SetProtectedPatient(const std::string& publicId,
1690 bool isProtected)
1691 {
1692 boost::mutex::scoped_lock lock(mutex_);
1693 Transaction transaction(*this);
1694
1695 // Lookup for the requested resource
1696 int64_t id;
1697 ResourceType type;
1698 if (!db_.LookupResource(id, type, publicId) ||
1699 type != ResourceType_Patient)
1700 {
1701 throw OrthancException(ErrorCode_ParameterOutOfRange);
1702 }
1703
1704 db_.SetProtectedPatient(id, isProtected);
1705 transaction.Commit(0);
1706
1707 if (isProtected)
1708 LOG(INFO) << "Patient " << publicId << " has been protected";
1709 else
1710 LOG(INFO) << "Patient " << publicId << " has been unprotected";
1711 }
1712
1713
1714 void ServerIndex::GetChildren(std::list<std::string>& result,
1715 const std::string& publicId)
1716 {
1717 result.clear();
1718
1719 boost::mutex::scoped_lock lock(mutex_);
1720
1721 ResourceType type;
1722 int64_t resource;
1723 if (!db_.LookupResource(resource, type, publicId))
1724 {
1725 throw OrthancException(ErrorCode_UnknownResource);
1726 }
1727
1728 if (type == ResourceType_Instance)
1729 {
1730 // An instance cannot have a child
1731 throw OrthancException(ErrorCode_BadParameterType);
1732 }
1733
1734 std::list<int64_t> tmp;
1735 db_.GetChildrenInternalId(tmp, resource);
1736
1737 for (std::list<int64_t>::const_iterator
1738 it = tmp.begin(); it != tmp.end(); ++it)
1739 {
1740 result.push_back(db_.GetPublicId(*it));
1741 }
1742 }
1743
1744
1745 void ServerIndex::GetChildInstances(std::list<std::string>& result,
1746 const std::string& publicId)
1747 {
1748 result.clear();
1749
1750 boost::mutex::scoped_lock lock(mutex_);
1751
1752 ResourceType type;
1753 int64_t top;
1754 if (!db_.LookupResource(top, type, publicId))
1755 {
1756 throw OrthancException(ErrorCode_UnknownResource);
1757 }
1758
1759 if (type == ResourceType_Instance)
1760 {
1761 // The resource is already an instance: Do not go down the hierarchy
1762 result.push_back(publicId);
1763 return;
1764 }
1765
1766 std::stack<int64_t> toExplore;
1767 toExplore.push(top);
1768
1769 std::list<int64_t> tmp;
1770
1771 while (!toExplore.empty())
1772 {
1773 // Get the internal ID of the current resource
1774 int64_t resource = toExplore.top();
1775 toExplore.pop();
1776
1777 if (db_.GetResourceType(resource) == ResourceType_Instance)
1778 {
1779 result.push_back(db_.GetPublicId(resource));
1780 }
1781 else
1782 {
1783 // Tag all the children of this resource as to be explored
1784 db_.GetChildrenInternalId(tmp, resource);
1785 for (std::list<int64_t>::const_iterator
1786 it = tmp.begin(); it != tmp.end(); ++it)
1787 {
1788 toExplore.push(*it);
1789 }
1790 }
1791 }
1792 }
1793
1794
1795 void ServerIndex::SetMetadata(const std::string& publicId,
1796 MetadataType type,
1797 const std::string& value)
1798 {
1799 boost::mutex::scoped_lock lock(mutex_);
1800 Transaction t(*this);
1801
1802 ResourceType rtype;
1803 int64_t id;
1804 if (!db_.LookupResource(id, rtype, publicId))
1805 {
1806 throw OrthancException(ErrorCode_UnknownResource);
1807 }
1808
1809 db_.SetMetadata(id, type, value);
1810
1811 if (IsUserMetadata(type))
1812 {
1813 LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId);
1814 }
1815
1816 t.Commit(0);
1817 }
1818
1819
1820 void ServerIndex::DeleteMetadata(const std::string& publicId,
1821 MetadataType type)
1822 {
1823 boost::mutex::scoped_lock lock(mutex_);
1824 Transaction t(*this);
1825
1826 ResourceType rtype;
1827 int64_t id;
1828 if (!db_.LookupResource(id, rtype, publicId))
1829 {
1830 throw OrthancException(ErrorCode_UnknownResource);
1831 }
1832
1833 db_.DeleteMetadata(id, type);
1834
1835 if (IsUserMetadata(type))
1836 {
1837 LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId);
1838 }
1839
1840 t.Commit(0);
1841 }
1842
1843
1844 bool ServerIndex::LookupMetadata(std::string& target,
1845 const std::string& publicId,
1846 MetadataType type)
1847 {
1848 boost::mutex::scoped_lock lock(mutex_);
1849
1850 ResourceType rtype;
1851 int64_t id;
1852 if (!db_.LookupResource(id, rtype, publicId))
1853 {
1854 throw OrthancException(ErrorCode_UnknownResource);
1855 }
1856
1857 return db_.LookupMetadata(target, id, type);
1858 }
1859
1860
1861 void ServerIndex::GetAllMetadata(std::map<MetadataType, std::string>& target,
1862 const std::string& publicId)
1863 {
1864 boost::mutex::scoped_lock lock(mutex_);
1865
1866 ResourceType type;
1867 int64_t id;
1868 if (!db_.LookupResource(id, type, publicId))
1869 {
1870 throw OrthancException(ErrorCode_UnknownResource);
1871 }
1872
1873 return db_.GetAllMetadata(target, id);
1874 }
1875
1876
1877 void ServerIndex::ListAvailableAttachments(std::list<FileContentType>& target,
1878 const std::string& publicId,
1879 ResourceType expectedType)
1880 {
1881 boost::mutex::scoped_lock lock(mutex_);
1882
1883 ResourceType type;
1884 int64_t id;
1885 if (!db_.LookupResource(id, type, publicId) ||
1886 expectedType != type)
1887 {
1888 throw OrthancException(ErrorCode_UnknownResource);
1889 }
1890
1891 db_.ListAvailableAttachments(target, id);
1892 }
1893
1894
1895 bool ServerIndex::LookupParent(std::string& target,
1896 const std::string& publicId)
1897 {
1898 boost::mutex::scoped_lock lock(mutex_);
1899
1900 ResourceType type;
1901 int64_t id;
1902 if (!db_.LookupResource(id, type, publicId))
1903 {
1904 throw OrthancException(ErrorCode_UnknownResource);
1905 }
1906
1907 int64_t parentId;
1908 if (db_.LookupParent(parentId, id))
1909 {
1910 target = db_.GetPublicId(parentId);
1911 return true;
1912 }
1913 else
1914 {
1915 return false;
1916 }
1917 }
1918
1919
1920 uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence)
1921 {
1922 boost::mutex::scoped_lock lock(mutex_);
1923 Transaction transaction(*this);
1924
1925 uint64_t seq = IncrementGlobalSequenceInternal(sequence);
1926 transaction.Commit(0);
1927
1928 return seq;
1929 }
1930
1931
1932
1933 void ServerIndex::LogChange(ChangeType changeType,
1934 const std::string& publicId)
1935 {
1936 boost::mutex::scoped_lock lock(mutex_);
1937 Transaction transaction(*this);
1938
1939 int64_t id;
1940 ResourceType type;
1941 if (!db_.LookupResource(id, type, publicId))
1942 {
1943 throw OrthancException(ErrorCode_UnknownResource);
1944 }
1945
1946 LogChange(id, changeType, type, publicId);
1947 transaction.Commit(0);
1948 }
1949
1950
1951 void ServerIndex::DeleteChanges()
1952 {
1953 boost::mutex::scoped_lock lock(mutex_);
1954
1955 Transaction transaction(*this);
1956 db_.ClearChanges();
1957 transaction.Commit(0);
1958 }
1959
1960 void ServerIndex::DeleteExportedResources()
1961 {
1962 boost::mutex::scoped_lock lock(mutex_);
1963
1964 Transaction transaction(*this);
1965 db_.ClearExportedResources();
1966 transaction.Commit(0);
1967 }
1968
1969
1970 void ServerIndex::GetResourceStatistics(/* out */ ResourceType& type,
1971 /* out */ uint64_t& diskSize,
1972 /* out */ uint64_t& uncompressedSize,
1973 /* out */ unsigned int& countStudies,
1974 /* out */ unsigned int& countSeries,
1975 /* out */ unsigned int& countInstances,
1976 /* out */ uint64_t& dicomDiskSize,
1977 /* out */ uint64_t& dicomUncompressedSize,
1978 const std::string& publicId)
1979 {
1980 boost::mutex::scoped_lock lock(mutex_);
1981
1982 int64_t top;
1983 if (!db_.LookupResource(top, type, publicId))
1984 {
1985 throw OrthancException(ErrorCode_UnknownResource);
1986 }
1987
1988 std::stack<int64_t> toExplore;
1989 toExplore.push(top);
1990
1991 countInstances = 0;
1992 countSeries = 0;
1993 countStudies = 0;
1994 diskSize = 0;
1995 uncompressedSize = 0;
1996 dicomDiskSize = 0;
1997 dicomUncompressedSize = 0;
1998
1999 while (!toExplore.empty())
2000 {
2001 // Get the internal ID of the current resource
2002 int64_t resource = toExplore.top();
2003 toExplore.pop();
2004
2005 ResourceType thisType = db_.GetResourceType(resource);
2006
2007 std::list<FileContentType> f;
2008 db_.ListAvailableAttachments(f, resource);
2009
2010 for (std::list<FileContentType>::const_iterator
2011 it = f.begin(); it != f.end(); ++it)
2012 {
2013 FileInfo attachment;
2014 if (db_.LookupAttachment(attachment, resource, *it))
2015 {
2016 if (attachment.GetContentType() == FileContentType_Dicom)
2017 {
2018 dicomDiskSize += attachment.GetCompressedSize();
2019 dicomUncompressedSize += attachment.GetUncompressedSize();
2020 }
2021
2022 diskSize += attachment.GetCompressedSize();
2023 uncompressedSize += attachment.GetUncompressedSize();
2024 }
2025 }
2026
2027 if (thisType == ResourceType_Instance)
2028 {
2029 countInstances++;
2030 }
2031 else
2032 {
2033 switch (thisType)
2034 {
2035 case ResourceType_Study:
2036 countStudies++;
2037 break;
2038
2039 case ResourceType_Series:
2040 countSeries++;
2041 break;
2042
2043 default:
2044 break;
2045 }
2046
2047 // Tag all the children of this resource as to be explored
2048 std::list<int64_t> tmp;
2049 db_.GetChildrenInternalId(tmp, resource);
2050 for (std::list<int64_t>::const_iterator
2051 it = tmp.begin(); it != tmp.end(); ++it)
2052 {
2053 toExplore.push(*it);
2054 }
2055 }
2056 }
2057
2058 if (countStudies == 0)
2059 {
2060 countStudies = 1;
2061 }
2062
2063 if (countSeries == 0)
2064 {
2065 countSeries = 1;
2066 }
2067 }
2068
2069
2070 void ServerIndex::UnstableResourcesMonitorThread(ServerIndex* that,
2071 unsigned int threadSleep)
2072 {
2073 int stableAge;
2074
2075 {
2076 OrthancConfiguration::ReaderLock lock;
2077 stableAge = lock.GetConfiguration().GetUnsignedIntegerParameter("StableAge", 60);
2078 }
2079
2080 if (stableAge <= 0)
2081 {
2082 stableAge = 60;
2083 }
2084
2085 LOG(INFO) << "Starting the monitor for stable resources (stable age = " << stableAge << ")";
2086
2087 while (!that->done_)
2088 {
2089 // Check for stable resources each few seconds
2090 boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleep));
2091
2092 boost::mutex::scoped_lock lock(that->mutex_);
2093
2094 while (!that->unstableResources_.IsEmpty() &&
2095 that->unstableResources_.GetOldestPayload().GetAge() > static_cast<unsigned int>(stableAge))
2096 {
2097 // This DICOM resource has not received any new instance for
2098 // some time. It can be considered as stable.
2099
2100 UnstableResourcePayload payload;
2101 int64_t id = that->unstableResources_.RemoveOldest(payload);
2102
2103 // Ensure that the resource is still existing before logging the change
2104 if (that->db_.IsExistingResource(id))
2105 {
2106 switch (payload.GetResourceType())
2107 {
2108 case ResourceType_Patient:
2109 that->LogChange(id, ChangeType_StablePatient, ResourceType_Patient, payload.GetPublicId());
2110 break;
2111
2112 case ResourceType_Study:
2113 that->LogChange(id, ChangeType_StableStudy, ResourceType_Study, payload.GetPublicId());
2114 break;
2115
2116 case ResourceType_Series:
2117 that->LogChange(id, ChangeType_StableSeries, ResourceType_Series, payload.GetPublicId());
2118 break;
2119
2120 default:
2121 throw OrthancException(ErrorCode_InternalError);
2122 }
2123
2124 //LOG(INFO) << "Stable resource: " << EnumerationToString(payload.type_) << " " << id;
2125 }
2126 }
2127 }
2128
2129 LOG(INFO) << "Closing the monitor thread for stable resources";
2130 }
2131
2132
2133 void ServerIndex::MarkAsUnstable(int64_t id,
2134 Orthanc::ResourceType type,
2135 const std::string& publicId)
2136 {
2137 // WARNING: Before calling this method, "mutex_" must be locked.
2138
2139 assert(type == Orthanc::ResourceType_Patient ||
2140 type == Orthanc::ResourceType_Study ||
2141 type == Orthanc::ResourceType_Series);
2142
2143 UnstableResourcePayload payload(type, publicId);
2144 unstableResources_.AddOrMakeMostRecent(id, payload);
2145 //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id;
2146
2147 LogChange(id, ChangeType_NewChildInstance, type, publicId);
2148 }
2149
2150
2151
2152 void ServerIndex::LookupIdentifierExact(std::vector<std::string>& result,
2153 ResourceType level,
2154 const DicomTag& tag,
2155 const std::string& value)
2156 {
2157 assert((level == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) ||
2158 (level == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) ||
2159 (level == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) ||
2160 (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
2161 (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
2162
2163 result.clear();
2164
2165 DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true);
2166
2167 std::vector<DatabaseConstraint> query;
2168 query.push_back(c.ConvertToDatabaseConstraint(level, DicomTagType_Identifier));
2169
2170 std::list<std::string> tmp;
2171
2172 {
2173 boost::mutex::scoped_lock lock(mutex_);
2174 db_.ApplyLookupResources(tmp, NULL, query, level, 0);
2175 }
2176
2177 CopyListToVector(result, tmp);
2178 }
2179
2180
2181 StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment,
2182 const std::string& publicId)
2183 {
2184 boost::mutex::scoped_lock lock(mutex_);
2185
2186 Transaction t(*this);
2187
2188 ResourceType resourceType;
2189 int64_t resourceId;
2190 if (!db_.LookupResource(resourceId, resourceType, publicId))
2191 {
2192 return StoreStatus_Failure; // Inexistent resource
2193 }
2194
2195 // Remove possible previous attachment
2196 db_.DeleteAttachment(resourceId, attachment.GetContentType());
2197
2198 // Locate the patient of the target resource
2199 int64_t patientId = resourceId;
2200 for (;;)
2201 {
2202 int64_t parent;
2203 if (db_.LookupParent(parent, patientId))
2204 {
2205 // We have not reached the patient level yet
2206 patientId = parent;
2207 }
2208 else
2209 {
2210 // We have reached the patient level
2211 break;
2212 }
2213 }
2214
2215 // Possibly apply the recycling mechanism while preserving this patient
2216 assert(db_.GetResourceType(patientId) == ResourceType_Patient);
2217 Recycle(attachment.GetCompressedSize(), db_.GetPublicId(patientId));
2218
2219 db_.AddAttachment(resourceId, attachment);
2220
2221 if (IsUserContentType(attachment.GetContentType()))
2222 {
2223 LogChange(resourceId, ChangeType_UpdatedAttachment, resourceType, publicId);
2224 }
2225
2226 t.Commit(attachment.GetCompressedSize());
2227
2228 return StoreStatus_Success;
2229 }
2230
2231
2232 void ServerIndex::DeleteAttachment(const std::string& publicId,
2233 FileContentType type)
2234 {
2235 boost::mutex::scoped_lock lock(mutex_);
2236 Transaction t(*this);
2237
2238 ResourceType rtype;
2239 int64_t id;
2240 if (!db_.LookupResource(id, rtype, publicId))
2241 {
2242 throw OrthancException(ErrorCode_UnknownResource);
2243 }
2244
2245 db_.DeleteAttachment(id, type);
2246
2247 if (IsUserContentType(type))
2248 {
2249 LogChange(id, ChangeType_UpdatedAttachment, rtype, publicId);
2250 }
2251
2252 t.Commit(0);
2253 }
2254
2255
2256 void ServerIndex::SetGlobalProperty(GlobalProperty property,
2257 const std::string& value)
2258 {
2259 boost::mutex::scoped_lock lock(mutex_);
2260
2261 Transaction transaction(*this);
2262 db_.SetGlobalProperty(property, value);
2263 transaction.Commit(0);
2264 }
2265
2266
2267 bool ServerIndex::LookupGlobalProperty(std::string& value,
2268 GlobalProperty property)
2269 {
2270 boost::mutex::scoped_lock lock(mutex_);
2271 return db_.LookupGlobalProperty(value, property);
2272 }
2273
2274
2275 std::string ServerIndex::GetGlobalProperty(GlobalProperty property,
2276 const std::string& defaultValue)
2277 {
2278 std::string value;
2279
2280 if (LookupGlobalProperty(value, property))
2281 {
2282 return value;
2283 }
2284 else
2285 {
2286 return defaultValue;
2287 }
2288 }
2289
2290
2291 bool ServerIndex::GetMainDicomTags(DicomMap& result,
2292 const std::string& publicId,
2293 ResourceType expectedType,
2294 ResourceType levelOfInterest)
2295 {
2296 // Yes, the following test could be shortened, but we wish to make it as clear as possible
2297 if (!(expectedType == ResourceType_Patient && levelOfInterest == ResourceType_Patient) &&
2298 !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Patient) &&
2299 !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Study) &&
2300 !(expectedType == ResourceType_Series && levelOfInterest == ResourceType_Series) &&
2301 !(expectedType == ResourceType_Instance && levelOfInterest == ResourceType_Instance))
2302 {
2303 throw OrthancException(ErrorCode_ParameterOutOfRange);
2304 }
2305
2306 result.Clear();
2307
2308 boost::mutex::scoped_lock lock(mutex_);
2309
2310 // Lookup for the requested resource
2311 int64_t id;
2312 ResourceType type;
2313 if (!db_.LookupResource(id, type, publicId) ||
2314 type != expectedType)
2315 {
2316 return false;
2317 }
2318
2319 if (type == ResourceType_Study)
2320 {
2321 DicomMap tmp;
2322 db_.GetMainDicomTags(tmp, id);
2323
2324 switch (levelOfInterest)
2325 {
2326 case ResourceType_Patient:
2327 tmp.ExtractPatientInformation(result);
2328 return true;
2329
2330 case ResourceType_Study:
2331 tmp.ExtractStudyInformation(result);
2332 return true;
2333
2334 default:
2335 throw OrthancException(ErrorCode_InternalError);
2336 }
2337 }
2338 else
2339 {
2340 db_.GetMainDicomTags(result, id);
2341 return true;
2342 }
2343 }
2344
2345
2346 bool ServerIndex::GetAllMainDicomTags(DicomMap& result,
2347 const std::string& instancePublicId)
2348 {
2349 result.Clear();
2350
2351 boost::mutex::scoped_lock lock(mutex_);
2352
2353 // Lookup for the requested resource
2354 int64_t instance;
2355 ResourceType type;
2356 if (!db_.LookupResource(instance, type, instancePublicId) ||
2357 type != ResourceType_Instance)
2358 {
2359 return false;
2360 }
2361 else
2362 {
2363 DicomMap tmp;
2364
2365 db_.GetMainDicomTags(tmp, instance);
2366 result.Merge(tmp);
2367
2368 int64_t series;
2369 if (!db_.LookupParent(series, instance))
2370 {
2371 throw OrthancException(ErrorCode_InternalError);
2372 }
2373
2374 tmp.Clear();
2375 db_.GetMainDicomTags(tmp, series);
2376 result.Merge(tmp);
2377
2378 int64_t study;
2379 if (!db_.LookupParent(study, series))
2380 {
2381 throw OrthancException(ErrorCode_InternalError);
2382 }
2383
2384 tmp.Clear();
2385 db_.GetMainDicomTags(tmp, study);
2386 result.Merge(tmp);
2387
2388 #ifndef NDEBUG
2389 {
2390 // Sanity test to check that all the main DICOM tags from the
2391 // patient level are copied at the study level
2392
2393 int64_t patient;
2394 if (!db_.LookupParent(patient, study))
2395 {
2396 throw OrthancException(ErrorCode_InternalError);
2397 }
2398
2399 tmp.Clear();
2400 db_.GetMainDicomTags(tmp, study);
2401
2402 std::set<DicomTag> patientTags;
2403 tmp.GetTags(patientTags);
2404
2405 for (std::set<DicomTag>::const_iterator
2406 it = patientTags.begin(); it != patientTags.end(); ++it)
2407 {
2408 assert(result.HasTag(*it));
2409 }
2410 }
2411 #endif
2412
2413 return true;
2414 }
2415 }
2416
2417
2418 bool ServerIndex::LookupResourceType(ResourceType& type,
2419 const std::string& publicId)
2420 {
2421 boost::mutex::scoped_lock lock(mutex_);
2422
2423 int64_t id;
2424 return db_.LookupResource(id, type, publicId);
2425 }
2426
2427
2428 unsigned int ServerIndex::GetDatabaseVersion()
2429 {
2430 boost::mutex::scoped_lock lock(mutex_);
2431 return db_.GetDatabaseVersion();
2432 }
2433
2434
2435 bool ServerIndex::LookupParent(std::string& target,
2436 const std::string& publicId,
2437 ResourceType parentType)
2438 {
2439 boost::mutex::scoped_lock lock(mutex_);
2440
2441 ResourceType type;
2442 int64_t id;
2443 if (!db_.LookupResource(id, type, publicId))
2444 {
2445 throw OrthancException(ErrorCode_UnknownResource);
2446 }
2447
2448 while (type != parentType)
2449 {
2450 int64_t parentId;
2451
2452 if (type == ResourceType_Patient || // Cannot further go up in hierarchy
2453 !db_.LookupParent(parentId, id))
2454 {
2455 return false;
2456 }
2457
2458 id = parentId;
2459 type = GetParentResourceType(type);
2460 }
2461
2462 target = db_.GetPublicId(id);
2463 return true;
2464 }
2465
2466
2467 void ServerIndex::ReconstructInstance(ParsedDicomFile& dicom)
2468 {
2469 DicomMap summary;
2470 dicom.ExtractDicomSummary(summary);
2471
2472 DicomInstanceHasher hasher(summary);
2473
2474 boost::mutex::scoped_lock lock(mutex_);
2475
2476 try
2477 {
2478 Transaction t(*this);
2479
2480 int64_t patient = -1, study = -1, series = -1, instance = -1;
2481
2482 ResourceType dummy;
2483 if (!db_.LookupResource(patient, dummy, hasher.HashPatient()) ||
2484 !db_.LookupResource(study, dummy, hasher.HashStudy()) ||
2485 !db_.LookupResource(series, dummy, hasher.HashSeries()) ||
2486 !db_.LookupResource(instance, dummy, hasher.HashInstance()) ||
2487 patient == -1 ||
2488 study == -1 ||
2489 series == -1 ||
2490 instance == -1)
2491 {
2492 throw OrthancException(ErrorCode_InternalError);
2493 }
2494
2495 db_.ClearMainDicomTags(patient);
2496 db_.ClearMainDicomTags(study);
2497 db_.ClearMainDicomTags(series);
2498 db_.ClearMainDicomTags(instance);
2499
2500 {
2501 ResourcesContent content;
2502 content.AddResource(patient, ResourceType_Patient, summary);
2503 content.AddResource(study, ResourceType_Study, summary);
2504 content.AddResource(series, ResourceType_Series, summary);
2505 content.AddResource(instance, ResourceType_Instance, summary);
2506 db_.SetResourcesContent(content);
2507 }
2508
2509 {
2510 std::string s;
2511 if (dicom.LookupTransferSyntax(s))
2512 {
2513 db_.SetMetadata(instance, MetadataType_Instance_TransferSyntax, s);
2514 }
2515 }
2516
2517 const DicomValue* value;
2518 if ((value = summary.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
2519 !value->IsNull() &&
2520 !value->IsBinary())
2521 {
2522 db_.SetMetadata(instance, MetadataType_Instance_SopClassUid, value->GetContent());
2523 }
2524
2525 t.Commit(0); // No change in the DB size
2526 }
2527 catch (OrthancException& e)
2528 {
2529 LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
2530 }
2531 }
2532
2533
2534 void ServerIndex::NormalizeLookup(std::vector<DatabaseConstraint>& target,
2535 const DatabaseLookup& source,
2536 ResourceType queryLevel) const
2537 {
2538 assert(mainDicomTagsRegistry_.get() != NULL);
2539
2540 target.clear();
2541 target.reserve(source.GetConstraintsCount());
2542
2543 for (size_t i = 0; i < source.GetConstraintsCount(); i++)
2544 {
2545 ResourceType level;
2546 DicomTagType type;
2547
2548 mainDicomTagsRegistry_->LookupTag(level, type, source.GetConstraint(i).GetTag());
2549
2550 if (type == DicomTagType_Identifier ||
2551 type == DicomTagType_Main)
2552 {
2553 // Use the fact that patient-level tags are copied at the study level
2554 if (level == ResourceType_Patient &&
2555 queryLevel != ResourceType_Patient)
2556 {
2557 level = ResourceType_Study;
2558 }
2559
2560 target.push_back(source.GetConstraint(i).ConvertToDatabaseConstraint(level, type));
2561 }
2562 }
2563 }
2564
2565
2566 void ServerIndex::ApplyLookupResources(std::vector<std::string>& resourcesId,
2567 std::vector<std::string>* instancesId,
2568 const DatabaseLookup& lookup,
2569 ResourceType queryLevel,
2570 size_t limit)
2571 {
2572 std::vector<DatabaseConstraint> normalized;
2573 NormalizeLookup(normalized, lookup, queryLevel);
2574
2575 std::list<std::string> resourcesList, instancesList;
2576
2577 {
2578 boost::mutex::scoped_lock lock(mutex_);
2579
2580 if (instancesId == NULL)
2581 {
2582 db_.ApplyLookupResources(resourcesList, NULL, normalized, queryLevel, limit);
2583 }
2584 else
2585 {
2586 db_.ApplyLookupResources(resourcesList, &instancesList, normalized, queryLevel, limit);
2587 }
2588 }
2589
2590 CopyListToVector(resourcesId, resourcesList);
2591
2592 if (instancesId != NULL)
2593 {
2594 CopyListToVector(*instancesId, instancesList);
2595 }
2596 }
2597 }