comparison OrthancServer/ServerIndex.cpp @ 1364:111e23bb4904 query-retrieve

integration mainline->query-retrieve
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 21 May 2015 16:58:30 +0200
parents c2c28dd17e87 216db29c5aa9
children b22ba8c5edbe
comparison
equal deleted inserted replaced
953:f894be6e7cc1 1364:111e23bb4904
1 /** 1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store 2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, 3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
4 * Belgium 4 * Department, University Hospital of Liege, Belgium
5 * 5 *
6 * This program is free software: you can redistribute it and/or 6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as 7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the 8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version. 9 * License, or (at your option) any later version.
35 35
36 #ifndef NOMINMAX 36 #ifndef NOMINMAX
37 #define NOMINMAX 37 #define NOMINMAX
38 #endif 38 #endif
39 39
40 #include "ServerIndexChange.h"
40 #include "EmbeddedResources.h" 41 #include "EmbeddedResources.h"
41 #include "OrthancInitialization.h" 42 #include "OrthancInitialization.h"
42 #include "../Core/Toolbox.h" 43 #include "../Core/Toolbox.h"
43 #include "../Core/Uuid.h" 44 #include "../Core/Uuid.h"
44 #include "../Core/DicomFormat/DicomArray.h" 45 #include "../Core/DicomFormat/DicomArray.h"
45 #include "../Core/SQLite/Transaction.h"
46 #include "FromDcmtkBridge.h" 46 #include "FromDcmtkBridge.h"
47 #include "ServerContext.h" 47 #include "ServerContext.h"
48 48
49 #include <boost/lexical_cast.hpp> 49 #include <boost/lexical_cast.hpp>
50 #include <stdio.h> 50 #include <stdio.h>
57 namespace Internals 57 namespace Internals
58 { 58 {
59 class ServerIndexListener : public IServerIndexListener 59 class ServerIndexListener : public IServerIndexListener
60 { 60 {
61 private: 61 private:
62 struct FileToRemove
63 {
64 private:
65 std::string uuid_;
66 FileContentType type_;
67
68 public:
69 FileToRemove(const FileInfo& info) : uuid_(info.GetUuid()),
70 type_(info.GetContentType())
71 {
72 }
73
74 const std::string& GetUuid() const
75 {
76 return uuid_;
77 }
78
79 FileContentType GetContentType() const
80 {
81 return type_;
82 }
83 };
84
62 ServerContext& context_; 85 ServerContext& context_;
63 bool hasRemainingLevel_; 86 bool hasRemainingLevel_;
64 ResourceType remainingType_; 87 ResourceType remainingType_;
65 std::string remainingPublicId_; 88 std::string remainingPublicId_;
66 std::list<std::string> pendingFilesToRemove_; 89 std::list<FileToRemove> pendingFilesToRemove_;
90 std::list<ServerIndexChange> pendingChanges_;
67 uint64_t sizeOfFilesToRemove_; 91 uint64_t sizeOfFilesToRemove_;
92 bool insideTransaction_;
93
94 void Reset()
95 {
96 sizeOfFilesToRemove_ = 0;
97 hasRemainingLevel_ = false;
98 pendingFilesToRemove_.clear();
99 pendingChanges_.clear();
100 }
68 101
69 public: 102 public:
70 ServerIndexListener(ServerContext& context) : 103 ServerIndexListener(ServerContext& context) : context_(context),
71 context_(context) 104 insideTransaction_(false)
72 { 105 {
73 Reset(); 106 Reset();
74 assert(ResourceType_Patient < ResourceType_Study && 107 assert(ResourceType_Patient < ResourceType_Study &&
75 ResourceType_Study < ResourceType_Series && 108 ResourceType_Study < ResourceType_Series &&
76 ResourceType_Series < ResourceType_Instance); 109 ResourceType_Series < ResourceType_Instance);
77 } 110 }
78 111
79 void Reset() 112 void StartTransaction()
80 { 113 {
81 sizeOfFilesToRemove_ = 0; 114 Reset();
82 hasRemainingLevel_ = false; 115 insideTransaction_ = true;
83 pendingFilesToRemove_.clear(); 116 }
117
118 void EndTransaction()
119 {
120 insideTransaction_ = false;
84 } 121 }
85 122
86 uint64_t GetSizeOfFilesToRemove() 123 uint64_t GetSizeOfFilesToRemove()
87 { 124 {
88 return sizeOfFilesToRemove_; 125 return sizeOfFilesToRemove_;
89 } 126 }
90 127
91 void CommitFilesToRemove() 128 void CommitFilesToRemove()
92 { 129 {
93 for (std::list<std::string>::iterator 130 for (std::list<FileToRemove>::const_iterator
94 it = pendingFilesToRemove_.begin(); 131 it = pendingFilesToRemove_.begin();
95 it != pendingFilesToRemove_.end(); ++it) 132 it != pendingFilesToRemove_.end(); ++it)
96 { 133 {
97 context_.RemoveFile(*it); 134 context_.RemoveFile(it->GetUuid(), it->GetContentType());
135 }
136 }
137
138 void CommitChanges()
139 {
140 for (std::list<ServerIndexChange>::const_iterator
141 it = pendingChanges_.begin();
142 it != pendingChanges_.end(); ++it)
143 {
144 context_.SignalChange(*it);
98 } 145 }
99 } 146 }
100 147
101 virtual void SignalRemainingAncestor(ResourceType parentType, 148 virtual void SignalRemainingAncestor(ResourceType parentType,
102 const std::string& publicId) 149 const std::string& publicId)
103 { 150 {
104 LOG(INFO) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")"; 151 VLOG(1) << "Remaining ancestor \"" << publicId << "\" (" << parentType << ")";
105 152
106 if (hasRemainingLevel_) 153 if (hasRemainingLevel_)
107 { 154 {
108 if (parentType < remainingType_) 155 if (parentType < remainingType_)
109 { 156 {
120 } 167 }
121 168
122 virtual void SignalFileDeleted(const FileInfo& info) 169 virtual void SignalFileDeleted(const FileInfo& info)
123 { 170 {
124 assert(Toolbox::IsUuid(info.GetUuid())); 171 assert(Toolbox::IsUuid(info.GetUuid()));
125 pendingFilesToRemove_.push_back(info.GetUuid()); 172 pendingFilesToRemove_.push_back(FileToRemove(info));
126 sizeOfFilesToRemove_ += info.GetCompressedSize(); 173 sizeOfFilesToRemove_ += info.GetCompressedSize();
174 }
175
176 virtual void SignalChange(const ServerIndexChange& change)
177 {
178 VLOG(1) << "Change related to resource " << change.GetPublicId() << " of type "
179 << EnumerationToString(change.GetResourceType()) << ": "
180 << EnumerationToString(change.GetChangeType());
181
182 if (insideTransaction_)
183 {
184 pendingChanges_.push_back(change);
185 }
186 else
187 {
188 context_.SignalChange(change);
189 }
127 } 190 }
128 191
129 bool HasRemainingLevel() const 192 bool HasRemainingLevel() const
130 { 193 {
131 return hasRemainingLevel_; 194 return hasRemainingLevel_;
148 211
149 class ServerIndex::Transaction 212 class ServerIndex::Transaction
150 { 213 {
151 private: 214 private:
152 ServerIndex& index_; 215 ServerIndex& index_;
153 std::auto_ptr<SQLite::Transaction> transaction_; 216 std::auto_ptr<SQLite::ITransaction> transaction_;
154 bool isCommitted_; 217 bool isCommitted_;
155 218
156 public: 219 public:
157 Transaction(ServerIndex& index) : 220 Transaction(ServerIndex& index) :
158 index_(index), 221 index_(index),
159 isCommitted_(false) 222 isCommitted_(false)
160 { 223 {
161 assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize()); 224 transaction_.reset(index_.db_.StartTransaction());
162
163 index_.listener_->Reset();
164 transaction_.reset(index_.db_->StartTransaction());
165 transaction_->Begin(); 225 transaction_->Begin();
226
227 assert(index_.currentStorageSize_ == index_.db_.GetTotalCompressedSize());
228
229 index_.listener_->StartTransaction();
230 }
231
232 ~Transaction()
233 {
234 index_.listener_->EndTransaction();
235
236 if (!isCommitted_)
237 {
238 transaction_->Rollback();
239 }
166 } 240 }
167 241
168 void Commit(uint64_t sizeOfAddedFiles) 242 void Commit(uint64_t sizeOfAddedFiles)
169 { 243 {
170 if (!isCommitted_) 244 if (!isCommitted_)
179 index_.currentStorageSize_ += sizeOfAddedFiles; 253 index_.currentStorageSize_ += sizeOfAddedFiles;
180 254
181 assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove()); 255 assert(index_.currentStorageSize_ >= index_.listener_->GetSizeOfFilesToRemove());
182 index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove(); 256 index_.currentStorageSize_ -= index_.listener_->GetSizeOfFilesToRemove();
183 257
184 assert(index_.currentStorageSize_ == index_.db_->GetTotalCompressedSize()); 258 // Send all the pending changes to the Orthanc plugins
259 index_.listener_->CommitChanges();
185 260
186 isCommitted_ = true; 261 isCommitted_ = true;
187 } 262 }
188 } 263 }
189 }; 264 };
190 265
191 266
192 struct ServerIndex::UnstableResourcePayload 267 class ServerIndex::UnstableResourcePayload
193 { 268 {
194 Orthanc::ResourceType type_; 269 private:
270 ResourceType type_;
271 std::string publicId_;
195 boost::posix_time::ptime time_; 272 boost::posix_time::ptime time_;
196 273
197 UnstableResourcePayload() : type_(Orthanc::ResourceType_Instance) 274 public:
198 { 275 UnstableResourcePayload() : type_(ResourceType_Instance)
199 } 276 {
200 277 }
201 UnstableResourcePayload(Orthanc::ResourceType type) : type_(type) 278
279 UnstableResourcePayload(Orthanc::ResourceType type,
280 const std::string& publicId) :
281 type_(type),
282 publicId_(publicId)
202 { 283 {
203 time_ = boost::posix_time::second_clock::local_time(); 284 time_ = boost::posix_time::second_clock::local_time();
204 } 285 }
205 286
206 unsigned int GetAge() const 287 unsigned int GetAge() const
207 { 288 {
208 return (boost::posix_time::second_clock::local_time() - time_).total_seconds(); 289 return (boost::posix_time::second_clock::local_time() - time_).total_seconds();
290 }
291
292 ResourceType GetResourceType() const
293 {
294 return type_;
295 }
296
297 const std::string& GetPublicId() const
298 {
299 return publicId_;
209 } 300 }
210 }; 301 };
211 302
212 303
213 bool ServerIndex::DeleteResource(Json::Value& target, 304 bool ServerIndex::DeleteResource(Json::Value& target,
214 const std::string& uuid, 305 const std::string& uuid,
215 ResourceType expectedType) 306 ResourceType expectedType)
216 { 307 {
217 boost::mutex::scoped_lock lock(mutex_); 308 boost::mutex::scoped_lock lock(mutex_);
218 listener_->Reset();
219 309
220 Transaction t(*this); 310 Transaction t(*this);
221 311
222 int64_t id; 312 int64_t id;
223 ResourceType type; 313 ResourceType type;
224 if (!db_->LookupResource(uuid, id, type) || 314 if (!db_.LookupResource(id, type, uuid) ||
225 expectedType != type) 315 expectedType != type)
226 { 316 {
227 return false; 317 return false;
228 } 318 }
229 319
230 db_->DeleteResource(id); 320 db_.DeleteResource(id);
231 321
232 if (listener_->HasRemainingLevel()) 322 if (listener_->HasRemainingLevel())
233 { 323 {
234 ResourceType type = listener_->GetRemainingType(); 324 ResourceType type = listener_->GetRemainingType();
235 const std::string& uuid = listener_->GetRemainingPublicId(); 325 const std::string& uuid = listener_->GetRemainingPublicId();
250 } 340 }
251 341
252 342
253 void ServerIndex::FlushThread(ServerIndex* that) 343 void ServerIndex::FlushThread(ServerIndex* that)
254 { 344 {
255 unsigned int sleep; 345 // By default, wait for 10 seconds before flushing
346 unsigned int sleep = 10;
256 347
257 try 348 try
258 { 349 {
259 boost::mutex::scoped_lock lock(that->mutex_); 350 boost::mutex::scoped_lock lock(that->mutex_);
260 std::string sleepString = that->db_->GetGlobalProperty(GlobalProperty_FlushSleep); 351 std::string sleepString;
261 sleep = boost::lexical_cast<unsigned int>(sleepString); 352
353 if (that->db_.LookupGlobalProperty(sleepString, GlobalProperty_FlushSleep) &&
354 Toolbox::IsInteger(sleepString))
355 {
356 sleep = boost::lexical_cast<unsigned int>(sleepString);
357 }
262 } 358 }
263 catch (boost::bad_lexical_cast&) 359 catch (boost::bad_lexical_cast&)
264 { 360 {
265 // By default, wait for 10 seconds before flushing
266 sleep = 10;
267 } 361 }
268 362
269 LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")"; 363 LOG(INFO) << "Starting the database flushing thread (sleep = " << sleep << ")";
270 364
271 unsigned int count = 0; 365 unsigned int count = 0;
278 { 372 {
279 continue; 373 continue;
280 } 374 }
281 375
282 boost::mutex::scoped_lock lock(that->mutex_); 376 boost::mutex::scoped_lock lock(that->mutex_);
283 that->db_->FlushToDisk(); 377 that->db_.FlushToDisk();
284 count = 0; 378 count = 0;
285 } 379 }
286 380
287 LOG(INFO) << "Stopping the database flushing thread"; 381 LOG(INFO) << "Stopping the database flushing thread";
288 } 382 }
289 383
290 384
291 static void ComputeExpectedNumberOfInstances(DatabaseWrapper& db, 385 static void ComputeExpectedNumberOfInstances(IDatabaseWrapper& db,
292 int64_t series, 386 int64_t series,
293 const DicomMap& dicomSummary) 387 const DicomMap& dicomSummary)
294 { 388 {
295 try 389 try
296 { 390 {
326 { 420 {
327 } 421 }
328 } 422 }
329 423
330 424
425
426
427 bool ServerIndex::GetMetadataAsInteger(int64_t& result,
428 int64_t id,
429 MetadataType type)
430 {
431 std::string s;
432 if (!db_.LookupMetadata(s, id, type))
433 {
434 return false;
435 }
436
437 try
438 {
439 result = boost::lexical_cast<int64_t>(s);
440 return true;
441 }
442 catch (boost::bad_lexical_cast&)
443 {
444 return false;
445 }
446 }
447
448
449 void ServerIndex::LogChange(int64_t internalId,
450 ChangeType changeType,
451 ResourceType resourceType,
452 const std::string& publicId)
453 {
454 ServerIndexChange change(changeType, resourceType, publicId);
455
456 if (changeType <= ChangeType_INTERNAL_LastLogged)
457 {
458 db_.LogChange(internalId, change);
459 }
460
461 assert(listener_.get() != NULL);
462 listener_->SignalChange(change);
463 }
464
465
466 uint64_t ServerIndex::IncrementGlobalSequenceInternal(GlobalProperty property)
467 {
468 std::string oldValue;
469
470 if (db_.LookupGlobalProperty(oldValue, property))
471 {
472 uint64_t oldNumber;
473
474 try
475 {
476 oldNumber = boost::lexical_cast<uint64_t>(oldValue);
477 db_.SetGlobalProperty(property, boost::lexical_cast<std::string>(oldNumber + 1));
478 return oldNumber + 1;
479 }
480 catch (boost::bad_lexical_cast&)
481 {
482 throw OrthancException(ErrorCode_InternalError);
483 }
484 }
485 else
486 {
487 // Initialize the sequence at "1"
488 db_.SetGlobalProperty(property, "1");
489 return 1;
490 }
491 }
492
493
494
495 void ServerIndex::SetMainDicomTags(int64_t resource,
496 const DicomMap& tags)
497 {
498 DicomArray flattened(tags);
499 for (size_t i = 0; i < flattened.GetSize(); i++)
500 {
501 const DicomElement& element = flattened.GetElement(i);
502 db_.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
503 }
504 }
505
506
507 int64_t ServerIndex::CreateResource(const std::string& publicId,
508 ResourceType type)
509 {
510 int64_t id = db_.CreateResource(publicId, type);
511
512 ChangeType changeType;
513 switch (type)
514 {
515 case ResourceType_Patient:
516 changeType = ChangeType_NewPatient;
517 break;
518
519 case ResourceType_Study:
520 changeType = ChangeType_NewStudy;
521 break;
522
523 case ResourceType_Series:
524 changeType = ChangeType_NewSeries;
525 break;
526
527 case ResourceType_Instance:
528 changeType = ChangeType_NewInstance;
529 break;
530
531 default:
532 throw OrthancException(ErrorCode_InternalError);
533 }
534
535 ServerIndexChange change(changeType, type, publicId);
536 db_.LogChange(id, change);
537
538 assert(listener_.get() != NULL);
539 listener_->SignalChange(change);
540
541 return id;
542 }
543
544
331 ServerIndex::ServerIndex(ServerContext& context, 545 ServerIndex::ServerIndex(ServerContext& context,
332 const std::string& dbPath) : 546 IDatabaseWrapper& db) :
333 done_(false), 547 done_(false),
548 db_(db),
334 maximumStorageSize_(0), 549 maximumStorageSize_(0),
335 maximumPatients_(0) 550 maximumPatients_(0)
336 { 551 {
337 listener_.reset(new Internals::ServerIndexListener(context)); 552 listener_.reset(new Internals::ServerIndexListener(context));
338 553 db_.SetListener(*listener_);
339 if (dbPath == ":memory:") 554
340 { 555 currentStorageSize_ = db_.GetTotalCompressedSize();
341 db_.reset(new DatabaseWrapper(*listener_));
342 }
343 else
344 {
345 boost::filesystem::path p = dbPath;
346
347 try
348 {
349 boost::filesystem::create_directories(p);
350 }
351 catch (boost::filesystem::filesystem_error)
352 {
353 }
354
355 db_.reset(new DatabaseWrapper(p.string() + "/index", *listener_));
356 }
357
358 currentStorageSize_ = db_->GetTotalCompressedSize();
359 556
360 // Initial recycling if the parameters have changed since the last 557 // Initial recycling if the parameters have changed since the last
361 // execution of Orthanc 558 // execution of Orthanc
362 StandaloneRecycling(); 559 StandaloneRecycling();
363 560
364 flushThread_ = boost::thread(FlushThread, this); 561 if (db.HasFlushToDisk())
562 {
563 flushThread_ = boost::thread(FlushThread, this);
564 }
565
365 unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this); 566 unstableResourcesMonitorThread_ = boost::thread(UnstableResourcesMonitorThread, this);
366 } 567 }
367 568
368 569
369 ServerIndex::~ServerIndex() 570 ServerIndex::~ServerIndex()
370 { 571 {
371 done_ = true; 572 done_ = true;
372 573
373 if (flushThread_.joinable()) 574 if (db_.HasFlushToDisk() &&
575 flushThread_.joinable())
374 { 576 {
375 flushThread_.join(); 577 flushThread_.join();
376 } 578 }
377 579
378 if (unstableResourcesMonitorThread_.joinable()) 580 if (unstableResourcesMonitorThread_.joinable())
380 unstableResourcesMonitorThread_.join(); 582 unstableResourcesMonitorThread_.join();
381 } 583 }
382 } 584 }
383 585
384 586
385 StoreStatus ServerIndex::Store(const DicomMap& dicomSummary, 587 StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
588 const DicomMap& dicomSummary,
386 const Attachments& attachments, 589 const Attachments& attachments,
387 const std::string& remoteAet) 590 const std::string& remoteAet,
388 { 591 const MetadataMap& metadata)
389 boost::mutex::scoped_lock lock(mutex_); 592 {
390 listener_->Reset(); 593 boost::mutex::scoped_lock lock(mutex_);
594
595 instanceMetadata.clear();
391 596
392 DicomInstanceHasher hasher(dicomSummary); 597 DicomInstanceHasher hasher(dicomSummary);
393 598
394 try 599 try
395 { 600 {
397 602
398 // Do nothing if the instance already exists 603 // Do nothing if the instance already exists
399 { 604 {
400 ResourceType type; 605 ResourceType type;
401 int64_t tmp; 606 int64_t tmp;
402 if (db_->LookupResource(hasher.HashInstance(), tmp, type)) 607 if (db_.LookupResource(tmp, type, hasher.HashInstance()))
403 { 608 {
404 assert(type == ResourceType_Instance); 609 assert(type == ResourceType_Instance);
610 db_.GetAllMetadata(instanceMetadata, tmp);
405 return StoreStatus_AlreadyStored; 611 return StoreStatus_AlreadyStored;
406 } 612 }
407 } 613 }
408 614
409 // Ensure there is enough room in the storage for the new instance 615 // Ensure there is enough room in the storage for the new instance
415 } 621 }
416 622
417 Recycle(instanceSize, hasher.HashPatient()); 623 Recycle(instanceSize, hasher.HashPatient());
418 624
419 // Create the instance 625 // Create the instance
420 int64_t instance = db_->CreateResource(hasher.HashInstance(), ResourceType_Instance); 626 int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance);
421 627
422 DicomMap dicom; 628 DicomMap dicom;
423 dicomSummary.ExtractInstanceInformation(dicom); 629 dicomSummary.ExtractInstanceInformation(dicom);
424 db_->SetMainDicomTags(instance, dicom); 630 SetMainDicomTags(instance, dicom);
425 631
426 // Detect up to which level the patient/study/series/instance 632 // Detect up to which level the patient/study/series/instance
427 // hierarchy must be created 633 // hierarchy must be created
428 int64_t patient = -1, study = -1, series = -1; 634 int64_t patient = -1, study = -1, series = -1;
429 bool isNewPatient = false; 635 bool isNewPatient = false;
431 bool isNewSeries = false; 637 bool isNewSeries = false;
432 638
433 { 639 {
434 ResourceType dummy; 640 ResourceType dummy;
435 641
436 if (db_->LookupResource(hasher.HashSeries(), series, dummy)) 642 if (db_.LookupResource(series, dummy, hasher.HashSeries()))
437 { 643 {
438 assert(dummy == ResourceType_Series); 644 assert(dummy == ResourceType_Series);
439 // The patient, the study and the series already exist 645 // The patient, the study and the series already exist
440 646
441 bool ok = (db_->LookupResource(hasher.HashPatient(), patient, dummy) && 647 bool ok = (db_.LookupResource(patient, dummy, hasher.HashPatient()) &&
442 db_->LookupResource(hasher.HashStudy(), study, dummy)); 648 db_.LookupResource(study, dummy, hasher.HashStudy()));
443 assert(ok); 649 assert(ok);
444 } 650 }
445 else if (db_->LookupResource(hasher.HashStudy(), study, dummy)) 651 else if (db_.LookupResource(study, dummy, hasher.HashStudy()))
446 { 652 {
447 assert(dummy == ResourceType_Study); 653 assert(dummy == ResourceType_Study);
448 654
449 // New series: The patient and the study already exist 655 // New series: The patient and the study already exist
450 isNewSeries = true; 656 isNewSeries = true;
451 657
452 bool ok = db_->LookupResource(hasher.HashPatient(), patient, dummy); 658 bool ok = db_.LookupResource(patient, dummy, hasher.HashPatient());
453 assert(ok); 659 assert(ok);
454 } 660 }
455 else if (db_->LookupResource(hasher.HashPatient(), patient, dummy)) 661 else if (db_.LookupResource(patient, dummy, hasher.HashPatient()))
456 { 662 {
457 assert(dummy == ResourceType_Patient); 663 assert(dummy == ResourceType_Patient);
458 664
459 // New study and series: The patient already exist 665 // New study and series: The patient already exist
460 isNewStudy = true; 666 isNewStudy = true;
470 } 676 }
471 677
472 // Create the series if needed 678 // Create the series if needed
473 if (isNewSeries) 679 if (isNewSeries)
474 { 680 {
475 series = db_->CreateResource(hasher.HashSeries(), ResourceType_Series); 681 series = CreateResource(hasher.HashSeries(), ResourceType_Series);
476 dicomSummary.ExtractSeriesInformation(dicom); 682 dicomSummary.ExtractSeriesInformation(dicom);
477 db_->SetMainDicomTags(series, dicom); 683 SetMainDicomTags(series, dicom);
478 } 684 }
479 685
480 // Create the study if needed 686 // Create the study if needed
481 if (isNewStudy) 687 if (isNewStudy)
482 { 688 {
483 study = db_->CreateResource(hasher.HashStudy(), ResourceType_Study); 689 study = CreateResource(hasher.HashStudy(), ResourceType_Study);
484 dicomSummary.ExtractStudyInformation(dicom); 690 dicomSummary.ExtractStudyInformation(dicom);
485 db_->SetMainDicomTags(study, dicom); 691 SetMainDicomTags(study, dicom);
486 } 692 }
487 693
488 // Create the patient if needed 694 // Create the patient if needed
489 if (isNewPatient) 695 if (isNewPatient)
490 { 696 {
491 patient = db_->CreateResource(hasher.HashPatient(), ResourceType_Patient); 697 patient = CreateResource(hasher.HashPatient(), ResourceType_Patient);
492 dicomSummary.ExtractPatientInformation(dicom); 698 dicomSummary.ExtractPatientInformation(dicom);
493 db_->SetMainDicomTags(patient, dicom); 699 SetMainDicomTags(patient, dicom);
494 } 700 }
495 701
496 // Create the parent-to-child links 702 // Create the parent-to-child links
497 db_->AttachChild(series, instance); 703 db_.AttachChild(series, instance);
498 704
499 if (isNewSeries) 705 if (isNewSeries)
500 { 706 {
501 db_->AttachChild(study, series); 707 db_.AttachChild(study, series);
502 } 708 }
503 709
504 if (isNewStudy) 710 if (isNewStudy)
505 { 711 {
506 db_->AttachChild(patient, study); 712 db_.AttachChild(patient, study);
507 } 713 }
508 714
509 // Sanity checks 715 // Sanity checks
510 assert(patient != -1); 716 assert(patient != -1);
511 assert(study != -1); 717 assert(study != -1);
514 720
515 // Attach the files to the newly created instance 721 // Attach the files to the newly created instance
516 for (Attachments::const_iterator it = attachments.begin(); 722 for (Attachments::const_iterator it = attachments.begin();
517 it != attachments.end(); ++it) 723 it != attachments.end(); ++it)
518 { 724 {
519 db_->AddAttachment(instance, *it); 725 db_.AddAttachment(instance, *it);
520 } 726 }
521 727
522 // Attach the metadata 728 // Attach the user-specified metadata
729 for (MetadataMap::const_iterator
730 it = metadata.begin(); it != metadata.end(); ++it)
731 {
732 switch (it->first.first)
733 {
734 case ResourceType_Patient:
735 db_.SetMetadata(patient, it->first.second, it->second);
736 break;
737
738 case ResourceType_Study:
739 db_.SetMetadata(study, it->first.second, it->second);
740 break;
741
742 case ResourceType_Series:
743 db_.SetMetadata(series, it->first.second, it->second);
744 break;
745
746 case ResourceType_Instance:
747 db_.SetMetadata(instance, it->first.second, it->second);
748 instanceMetadata[it->first.second] = it->second;
749 break;
750
751 default:
752 throw OrthancException(ErrorCode_ParameterOutOfRange);
753 }
754 }
755
756 // Attach the auto-computed metadata for the patient/study/series levels
523 std::string now = Toolbox::GetNowIsoString(); 757 std::string now = Toolbox::GetNowIsoString();
524 db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now); 758 db_.SetMetadata(series, MetadataType_LastUpdate, now);
525 db_->SetMetadata(series, MetadataType_LastUpdate, now); 759 db_.SetMetadata(study, MetadataType_LastUpdate, now);
526 db_->SetMetadata(study, MetadataType_LastUpdate, now); 760 db_.SetMetadata(patient, MetadataType_LastUpdate, now);
527 db_->SetMetadata(patient, MetadataType_LastUpdate, now); 761
528 db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet); 762 // Attach the auto-computed metadata for the instance level,
763 // reflecting these additions into the input metadata map
764 db_.SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
765 instanceMetadata[MetadataType_Instance_ReceptionDate] = now;
766
767 db_.SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
768 instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet;
529 769
530 const DicomValue* value; 770 const DicomValue* value;
531 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || 771 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
532 (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) 772 (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
533 { 773 {
534 db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString()); 774 db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
535 } 775 instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString();
536 776 }
777
778 // Check whether the series of this new instance is now completed
537 if (isNewSeries) 779 if (isNewSeries)
538 { 780 {
539 ComputeExpectedNumberOfInstances(*db_, series, dicomSummary); 781 ComputeExpectedNumberOfInstances(db_, series, dicomSummary);
540 } 782 }
541 783
542 // Check whether the series of this new instance is now completed
543 SeriesStatus seriesStatus = GetSeriesStatus(series); 784 SeriesStatus seriesStatus = GetSeriesStatus(series);
544 if (seriesStatus == SeriesStatus_Complete) 785 if (seriesStatus == SeriesStatus_Complete)
545 { 786 {
546 db_->LogChange(ChangeType_CompletedSeries, series, ResourceType_Series); 787 LogChange(series, ChangeType_CompletedSeries, ResourceType_Series, hasher.HashSeries());
547 } 788 }
548 789
549 // Mark the parent resources of this instance as unstable 790 // Mark the parent resources of this instance as unstable
550 MarkAsUnstable(patient, ResourceType_Patient); 791 MarkAsUnstable(series, ResourceType_Series, hasher.HashSeries());
551 MarkAsUnstable(study, ResourceType_Study); 792 MarkAsUnstable(study, ResourceType_Study, hasher.HashStudy());
552 MarkAsUnstable(series, ResourceType_Series); 793 MarkAsUnstable(patient, ResourceType_Patient, hasher.HashPatient());
553 794
554 t.Commit(instanceSize); 795 t.Commit(instanceSize);
555 796
556 return StoreStatus_Success; 797 return StoreStatus_Success;
557 } 798 }
558 catch (OrthancException& e) 799 catch (OrthancException& e)
559 { 800 {
560 LOG(ERROR) << "EXCEPTION [" << e.What() << "]" 801 LOG(ERROR) << "EXCEPTION [" << e.What() << "]";
561 << " (SQLite status: " << db_->GetErrorMessage() << ")";
562 } 802 }
563 803
564 return StoreStatus_Failure; 804 return StoreStatus_Failure;
565 } 805 }
566 806
569 { 809 {
570 boost::mutex::scoped_lock lock(mutex_); 810 boost::mutex::scoped_lock lock(mutex_);
571 target = Json::objectValue; 811 target = Json::objectValue;
572 812
573 uint64_t cs = currentStorageSize_; 813 uint64_t cs = currentStorageSize_;
574 assert(cs == db_->GetTotalCompressedSize()); 814 assert(cs == db_.GetTotalCompressedSize());
575 uint64_t us = db_->GetTotalUncompressedSize(); 815 uint64_t us = db_.GetTotalUncompressedSize();
576 target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs); 816 target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs);
577 target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us); 817 target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
578 target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES); 818 target["TotalDiskSizeMB"] = static_cast<unsigned int>(cs / MEGA_BYTES);
579 target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES); 819 target["TotalUncompressedSizeMB"] = static_cast<unsigned int>(us / MEGA_BYTES);
580 820
581 target["CountPatients"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Patient)); 821 target["CountPatients"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Patient));
582 target["CountStudies"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Study)); 822 target["CountStudies"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Study));
583 target["CountSeries"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Series)); 823 target["CountSeries"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Series));
584 target["CountInstances"] = static_cast<unsigned int>(db_->GetResourceCount(ResourceType_Instance)); 824 target["CountInstances"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Instance));
585 } 825 }
586 826
587 827
588 828
589 SeriesStatus ServerIndex::GetSeriesStatus(int64_t id) 829 SeriesStatus ServerIndex::GetSeriesStatus(int64_t id)
590 { 830 {
591 // Get the expected number of instances in this series (from the metadata) 831 // Get the expected number of instances in this series (from the metadata)
592 std::string s = db_->GetMetadata(id, MetadataType_Series_ExpectedNumberOfInstances); 832 int64_t expected;
593 833 if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances))
594 size_t expected;
595 try
596 {
597 expected = boost::lexical_cast<size_t>(s);
598 }
599 catch (boost::bad_lexical_cast&)
600 { 834 {
601 return SeriesStatus_Unknown; 835 return SeriesStatus_Unknown;
602 } 836 }
603 837
604 // Loop over the instances of this series 838 // Loop over the instances of this series
605 std::list<int64_t> children; 839 std::list<int64_t> children;
606 db_->GetChildrenInternalId(children, id); 840 db_.GetChildrenInternalId(children, id);
607 841
608 std::set<size_t> instances; 842 std::set<int64_t> instances;
609 for (std::list<int64_t>::const_iterator 843 for (std::list<int64_t>::const_iterator
610 it = children.begin(); it != children.end(); ++it) 844 it = children.begin(); it != children.end(); ++it)
611 { 845 {
612 // Get the index of this instance in the series 846 // Get the index of this instance in the series
613 s = db_->GetMetadata(*it, MetadataType_Instance_IndexInSeries); 847 int64_t index;
614 size_t index; 848 if (!GetMetadataAsInteger(index, *it, MetadataType_Instance_IndexInSeries))
615 try
616 {
617 index = boost::lexical_cast<size_t>(s);
618 }
619 catch (boost::bad_lexical_cast&)
620 { 849 {
621 return SeriesStatus_Unknown; 850 return SeriesStatus_Unknown;
622 } 851 }
623 852
624 if (!(index > 0 && index <= expected)) 853 if (!(index > 0 && index <= expected))
634 } 863 }
635 864
636 instances.insert(index); 865 instances.insert(index);
637 } 866 }
638 867
639 if (instances.size() == expected) 868 if (static_cast<int64_t>(instances.size()) == expected)
640 { 869 {
641 return SeriesStatus_Complete; 870 return SeriesStatus_Complete;
642 } 871 }
643 else 872 else
644 { 873 {
650 879
651 void ServerIndex::MainDicomTagsToJson(Json::Value& target, 880 void ServerIndex::MainDicomTagsToJson(Json::Value& target,
652 int64_t resourceId) 881 int64_t resourceId)
653 { 882 {
654 DicomMap tags; 883 DicomMap tags;
655 db_->GetMainDicomTags(tags, resourceId); 884 db_.GetMainDicomTags(tags, resourceId);
656 target["MainDicomTags"] = Json::objectValue; 885 target["MainDicomTags"] = Json::objectValue;
657 FromDcmtkBridge::ToJson(target["MainDicomTags"], tags); 886 FromDcmtkBridge::ToJson(target["MainDicomTags"], tags);
658 } 887 }
659 888
660 bool ServerIndex::LookupResource(Json::Value& result, 889 bool ServerIndex::LookupResource(Json::Value& result,
666 boost::mutex::scoped_lock lock(mutex_); 895 boost::mutex::scoped_lock lock(mutex_);
667 896
668 // Lookup for the requested resource 897 // Lookup for the requested resource
669 int64_t id; 898 int64_t id;
670 ResourceType type; 899 ResourceType type;
671 if (!db_->LookupResource(publicId, id, type) || 900 if (!db_.LookupResource(id, type, publicId) ||
672 type != expectedType) 901 type != expectedType)
673 { 902 {
674 return false; 903 return false;
675 } 904 }
676 905
677 // Find the parent resource (if it exists) 906 // Find the parent resource (if it exists)
678 if (type != ResourceType_Patient) 907 if (type != ResourceType_Patient)
679 { 908 {
680 int64_t parentId; 909 int64_t parentId;
681 if (!db_->LookupParent(parentId, id)) 910 if (!db_.LookupParent(parentId, id))
682 { 911 {
683 throw OrthancException(ErrorCode_InternalError); 912 throw OrthancException(ErrorCode_InternalError);
684 } 913 }
685 914
686 std::string parent = db_->GetPublicId(parentId); 915 std::string parent = db_.GetPublicId(parentId);
687 916
688 switch (type) 917 switch (type)
689 { 918 {
690 case ResourceType_Study: 919 case ResourceType_Study:
691 result["ParentPatient"] = parent; 920 result["ParentPatient"] = parent;
704 } 933 }
705 } 934 }
706 935
707 // List the children resources 936 // List the children resources
708 std::list<std::string> children; 937 std::list<std::string> children;
709 db_->GetChildrenPublicId(children, id); 938 db_.GetChildrenPublicId(children, id);
710 939
711 if (type != ResourceType_Instance) 940 if (type != ResourceType_Instance)
712 { 941 {
713 Json::Value c = Json::arrayValue; 942 Json::Value c = Json::arrayValue;
714 943
751 case ResourceType_Series: 980 case ResourceType_Series:
752 { 981 {
753 result["Type"] = "Series"; 982 result["Type"] = "Series";
754 result["Status"] = EnumerationToString(GetSeriesStatus(id)); 983 result["Status"] = EnumerationToString(GetSeriesStatus(id));
755 984
756 int i; 985 int64_t i;
757 if (db_->GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances)) 986 if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
758 result["ExpectedNumberOfInstances"] = i; 987 result["ExpectedNumberOfInstances"] = static_cast<int>(i);
759 else 988 else
760 result["ExpectedNumberOfInstances"] = Json::nullValue; 989 result["ExpectedNumberOfInstances"] = Json::nullValue;
761 990
762 break; 991 break;
763 } 992 }
765 case ResourceType_Instance: 994 case ResourceType_Instance:
766 { 995 {
767 result["Type"] = "Instance"; 996 result["Type"] = "Instance";
768 997
769 FileInfo attachment; 998 FileInfo attachment;
770 if (!db_->LookupAttachment(attachment, id, FileContentType_Dicom)) 999 if (!db_.LookupAttachment(attachment, id, FileContentType_Dicom))
771 { 1000 {
772 throw OrthancException(ErrorCode_InternalError); 1001 throw OrthancException(ErrorCode_InternalError);
773 } 1002 }
774 1003
775 result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize()); 1004 result["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize());
776 result["FileUuid"] = attachment.GetUuid(); 1005 result["FileUuid"] = attachment.GetUuid();
777 1006
778 int i; 1007 int64_t i;
779 if (db_->GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries)) 1008 if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
780 result["IndexInSeries"] = i; 1009 result["IndexInSeries"] = static_cast<int>(i);
781 else 1010 else
782 result["IndexInSeries"] = Json::nullValue; 1011 result["IndexInSeries"] = Json::nullValue;
783 1012
784 break; 1013 break;
785 } 1014 }
792 result["ID"] = publicId; 1021 result["ID"] = publicId;
793 MainDicomTagsToJson(result, id); 1022 MainDicomTagsToJson(result, id);
794 1023
795 std::string tmp; 1024 std::string tmp;
796 1025
797 tmp = db_->GetMetadata(id, MetadataType_AnonymizedFrom); 1026 if (db_.LookupMetadata(tmp, id, MetadataType_AnonymizedFrom))
798 if (tmp.size() != 0) 1027 {
799 result["AnonymizedFrom"] = tmp; 1028 result["AnonymizedFrom"] = tmp;
800 1029 }
801 tmp = db_->GetMetadata(id, MetadataType_ModifiedFrom); 1030
802 if (tmp.size() != 0) 1031 if (db_.LookupMetadata(tmp, id, MetadataType_ModifiedFrom))
1032 {
803 result["ModifiedFrom"] = tmp; 1033 result["ModifiedFrom"] = tmp;
1034 }
804 1035
805 if (type == ResourceType_Patient || 1036 if (type == ResourceType_Patient ||
806 type == ResourceType_Study || 1037 type == ResourceType_Study ||
807 type == ResourceType_Series) 1038 type == ResourceType_Series)
808 { 1039 {
809 result["IsStable"] = !unstableResources_.Contains(id); 1040 result["IsStable"] = !unstableResources_.Contains(id);
1041
1042 if (db_.LookupMetadata(tmp, id, MetadataType_LastUpdate))
1043 {
1044 result["LastUpdate"] = tmp;
1045 }
810 } 1046 }
811 1047
812 return true; 1048 return true;
813 } 1049 }
814 1050
819 { 1055 {
820 boost::mutex::scoped_lock lock(mutex_); 1056 boost::mutex::scoped_lock lock(mutex_);
821 1057
822 int64_t id; 1058 int64_t id;
823 ResourceType type; 1059 ResourceType type;
824 if (!db_->LookupResource(instanceUuid, id, type)) 1060 if (!db_.LookupResource(id, type, instanceUuid))
825 { 1061 {
826 throw OrthancException(ErrorCode_InternalError); 1062 throw OrthancException(ErrorCode_UnknownResource);
827 } 1063 }
828 1064
829 if (db_->LookupAttachment(attachment, id, contentType)) 1065 if (db_.LookupAttachment(attachment, id, contentType))
830 { 1066 {
831 assert(attachment.GetContentType() == contentType); 1067 assert(attachment.GetContentType() == contentType);
832 return true; 1068 return true;
833 } 1069 }
834 else 1070 else
837 } 1073 }
838 } 1074 }
839 1075
840 1076
841 1077
842 void ServerIndex::GetAllUuids(Json::Value& target, 1078 void ServerIndex::GetAllUuids(std::list<std::string>& target,
843 ResourceType resourceType) 1079 ResourceType resourceType)
844 { 1080 {
845 boost::mutex::scoped_lock lock(mutex_); 1081 boost::mutex::scoped_lock lock(mutex_);
846 db_->GetAllPublicIds(target, resourceType); 1082 db_.GetAllPublicIds(target, resourceType);
847 } 1083 }
848 1084
849 1085
850 bool ServerIndex::GetChanges(Json::Value& target, 1086 template <typename T>
1087 static void FormatLog(Json::Value& target,
1088 const std::list<T>& log,
1089 const std::string& name,
1090 bool done,
1091 int64_t since)
1092 {
1093 Json::Value items = Json::arrayValue;
1094 for (typename std::list<T>::const_iterator
1095 it = log.begin(); it != log.end(); ++it)
1096 {
1097 Json::Value item;
1098 it->Format(item);
1099 items.append(item);
1100 }
1101
1102 target = Json::objectValue;
1103 target[name] = items;
1104 target["Done"] = done;
1105
1106 int64_t last = (log.empty() ? since : log.back().GetSeq());
1107 target["Last"] = static_cast<int>(last);
1108 }
1109
1110
1111 void ServerIndex::GetChanges(Json::Value& target,
851 int64_t since, 1112 int64_t since,
852 unsigned int maxResults) 1113 unsigned int maxResults)
853 { 1114 {
854 boost::mutex::scoped_lock lock(mutex_); 1115 std::list<ServerIndexChange> changes;
855 db_->GetChanges(target, since, maxResults); 1116 bool done;
856 return true; 1117
857 } 1118 {
858 1119 boost::mutex::scoped_lock lock(mutex_);
859 bool ServerIndex::GetLastChange(Json::Value& target) 1120 db_.GetChanges(changes, done, since, maxResults);
860 { 1121 }
861 boost::mutex::scoped_lock lock(mutex_); 1122
862 db_->GetLastChange(target); 1123 FormatLog(target, changes, "Changes", done, since);
863 return true; 1124 }
864 } 1125
1126
1127 void ServerIndex::GetLastChange(Json::Value& target)
1128 {
1129 std::list<ServerIndexChange> changes;
1130
1131 {
1132 boost::mutex::scoped_lock lock(mutex_);
1133 db_.GetLastChange(changes);
1134 }
1135
1136 FormatLog(target, changes, "Changes", true, 0);
1137 }
1138
865 1139
866 void ServerIndex::LogExportedResource(const std::string& publicId, 1140 void ServerIndex::LogExportedResource(const std::string& publicId,
867 const std::string& remoteModality) 1141 const std::string& remoteModality)
868 { 1142 {
869 boost::mutex::scoped_lock lock(mutex_); 1143 boost::mutex::scoped_lock lock(mutex_);
1144 Transaction transaction(*this);
870 1145
871 int64_t id; 1146 int64_t id;
872 ResourceType type; 1147 ResourceType type;
873 if (!db_->LookupResource(publicId, id, type)) 1148 if (!db_.LookupResource(id, type, publicId))
874 { 1149 {
875 throw OrthancException(ErrorCode_InternalError); 1150 throw OrthancException(ErrorCode_InternalError);
876 } 1151 }
877 1152
878 std::string patientId; 1153 std::string patientId;
886 // Iteratively go up inside the patient/study/series/instance hierarchy 1161 // Iteratively go up inside the patient/study/series/instance hierarchy
887 bool done = false; 1162 bool done = false;
888 while (!done) 1163 while (!done)
889 { 1164 {
890 DicomMap map; 1165 DicomMap map;
891 db_->GetMainDicomTags(map, currentId); 1166 db_.GetMainDicomTags(map, currentId);
892 1167
893 switch (currentType) 1168 switch (currentType)
894 { 1169 {
895 case ResourceType_Patient: 1170 case ResourceType_Patient:
896 patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString(); 1171 patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString();
918 1193
919 // If we have not reached the Patient level, find the parent of 1194 // If we have not reached the Patient level, find the parent of
920 // the current resource 1195 // the current resource
921 if (!done) 1196 if (!done)
922 { 1197 {
923 bool ok = db_->LookupParent(currentId, currentId); 1198 bool ok = db_.LookupParent(currentId, currentId);
924 assert(ok); 1199 assert(ok);
925 } 1200 }
926 } 1201 }
927 1202
928 // No need for a SQLite::Transaction here, as we only insert 1 record 1203 ExportedResource resource(-1,
929 db_->LogExportedResource(type, 1204 type,
930 publicId, 1205 publicId,
931 remoteModality, 1206 remoteModality,
932 patientId, 1207 Toolbox::GetNowIsoString(),
933 studyInstanceUid, 1208 patientId,
934 seriesInstanceUid, 1209 studyInstanceUid,
935 sopInstanceUid); 1210 seriesInstanceUid,
936 } 1211 sopInstanceUid);
937 1212
938 1213 db_.LogExportedResource(resource);
939 bool ServerIndex::GetExportedResources(Json::Value& target, 1214 transaction.Commit(0);
1215 }
1216
1217
1218 void ServerIndex::GetExportedResources(Json::Value& target,
940 int64_t since, 1219 int64_t since,
941 unsigned int maxResults) 1220 unsigned int maxResults)
942 { 1221 {
943 boost::mutex::scoped_lock lock(mutex_); 1222 std::list<ExportedResource> exported;
944 db_->GetExportedResources(target, since, maxResults); 1223 bool done;
945 return true; 1224
946 } 1225 {
947 1226 boost::mutex::scoped_lock lock(mutex_);
948 bool ServerIndex::GetLastExportedResource(Json::Value& target) 1227 db_.GetExportedResources(exported, done, since, maxResults);
949 { 1228 }
950 boost::mutex::scoped_lock lock(mutex_); 1229
951 db_->GetLastExportedResource(target); 1230 FormatLog(target, exported, "Exports", done, since);
952 return true; 1231 }
1232
1233
1234 void ServerIndex::GetLastExportedResource(Json::Value& target)
1235 {
1236 std::list<ExportedResource> exported;
1237
1238 {
1239 boost::mutex::scoped_lock lock(mutex_);
1240 db_.GetLastExportedResource(exported);
1241 }
1242
1243 FormatLog(target, exported, "Exports", true, 0);
953 } 1244 }
954 1245
955 1246
956 bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize) 1247 bool ServerIndex::IsRecyclingNeeded(uint64_t instanceSize)
957 { 1248 {
958 if (maximumStorageSize_ != 0) 1249 if (maximumStorageSize_ != 0)
959 { 1250 {
960 uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove(); 1251 uint64_t currentSize = currentStorageSize_ - listener_->GetSizeOfFilesToRemove();
961 assert(db_->GetTotalCompressedSize() == currentSize); 1252 assert(db_.GetTotalCompressedSize() == currentSize);
962 1253
963 if (currentSize + instanceSize > maximumStorageSize_) 1254 if (currentSize + instanceSize > maximumStorageSize_)
964 { 1255 {
965 return true; 1256 return true;
966 } 1257 }
967 } 1258 }
968 1259
969 if (maximumPatients_ != 0) 1260 if (maximumPatients_ != 0)
970 { 1261 {
971 uint64_t patientCount = db_->GetResourceCount(ResourceType_Patient); 1262 uint64_t patientCount = db_.GetResourceCount(ResourceType_Patient);
972 if (patientCount > maximumPatients_) 1263 if (patientCount > maximumPatients_)
973 { 1264 {
974 return true; 1265 return true;
975 } 1266 }
976 } 1267 }
989 1280
990 // Check whether other DICOM instances from this patient are 1281 // Check whether other DICOM instances from this patient are
991 // already stored 1282 // already stored
992 int64_t patientToAvoid; 1283 int64_t patientToAvoid;
993 ResourceType type; 1284 ResourceType type;
994 bool hasPatientToAvoid = db_->LookupResource(newPatientId, patientToAvoid, type); 1285 bool hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId);
995 1286
996 if (hasPatientToAvoid && type != ResourceType_Patient) 1287 if (hasPatientToAvoid && type != ResourceType_Patient)
997 { 1288 {
998 throw OrthancException(ErrorCode_InternalError); 1289 throw OrthancException(ErrorCode_InternalError);
999 } 1290 }
1004 while (true) 1295 while (true)
1005 { 1296 {
1006 // If other instances of this patient are already in the store, 1297 // If other instances of this patient are already in the store,
1007 // we must avoid to recycle them 1298 // we must avoid to recycle them
1008 bool ok = hasPatientToAvoid ? 1299 bool ok = hasPatientToAvoid ?
1009 db_->SelectPatientToRecycle(patientToRecycle, patientToAvoid) : 1300 db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
1010 db_->SelectPatientToRecycle(patientToRecycle); 1301 db_.SelectPatientToRecycle(patientToRecycle);
1011 1302
1012 if (!ok) 1303 if (!ok)
1013 { 1304 {
1014 throw OrthancException(ErrorCode_FullStorage); 1305 throw OrthancException(ErrorCode_FullStorage);
1015 } 1306 }
1016 1307
1017 LOG(INFO) << "Recycling one patient"; 1308 VLOG(1) << "Recycling one patient";
1018 db_->DeleteResource(patientToRecycle); 1309 db_.DeleteResource(patientToRecycle);
1019 1310
1020 if (!IsRecyclingNeeded(instanceSize)) 1311 if (!IsRecyclingNeeded(instanceSize))
1021 { 1312 {
1022 // OK, we're done 1313 // OK, we're done
1023 break; 1314 break;
1073 boost::mutex::scoped_lock lock(mutex_); 1364 boost::mutex::scoped_lock lock(mutex_);
1074 1365
1075 // Lookup for the requested resource 1366 // Lookup for the requested resource
1076 int64_t id; 1367 int64_t id;
1077 ResourceType type; 1368 ResourceType type;
1078 if (!db_->LookupResource(publicId, id, type) || 1369 if (!db_.LookupResource(id, type, publicId) ||
1079 type != ResourceType_Patient) 1370 type != ResourceType_Patient)
1080 { 1371 {
1081 throw OrthancException(ErrorCode_ParameterOutOfRange); 1372 throw OrthancException(ErrorCode_ParameterOutOfRange);
1082 } 1373 }
1083 1374
1084 return db_->IsProtectedPatient(id); 1375 return db_.IsProtectedPatient(id);
1085 } 1376 }
1086 1377
1087 1378
1088 void ServerIndex::SetProtectedPatient(const std::string& publicId, 1379 void ServerIndex::SetProtectedPatient(const std::string& publicId,
1089 bool isProtected) 1380 bool isProtected)
1090 { 1381 {
1091 boost::mutex::scoped_lock lock(mutex_); 1382 boost::mutex::scoped_lock lock(mutex_);
1383 Transaction transaction(*this);
1092 1384
1093 // Lookup for the requested resource 1385 // Lookup for the requested resource
1094 int64_t id; 1386 int64_t id;
1095 ResourceType type; 1387 ResourceType type;
1096 if (!db_->LookupResource(publicId, id, type) || 1388 if (!db_.LookupResource(id, type, publicId) ||
1097 type != ResourceType_Patient) 1389 type != ResourceType_Patient)
1098 { 1390 {
1099 throw OrthancException(ErrorCode_ParameterOutOfRange); 1391 throw OrthancException(ErrorCode_ParameterOutOfRange);
1100 } 1392 }
1101 1393
1102 // No need for a SQLite::Transaction here, as we only make 1 write to the DB 1394 db_.SetProtectedPatient(id, isProtected);
1103 db_->SetProtectedPatient(id, isProtected); 1395 transaction.Commit(0);
1104 1396
1105 if (isProtected) 1397 if (isProtected)
1106 LOG(INFO) << "Patient " << publicId << " has been protected"; 1398 LOG(INFO) << "Patient " << publicId << " has been protected";
1107 else 1399 else
1108 LOG(INFO) << "Patient " << publicId << " has been unprotected"; 1400 LOG(INFO) << "Patient " << publicId << " has been unprotected";
1116 1408
1117 boost::mutex::scoped_lock lock(mutex_); 1409 boost::mutex::scoped_lock lock(mutex_);
1118 1410
1119 ResourceType type; 1411 ResourceType type;
1120 int64_t resource; 1412 int64_t resource;
1121 if (!db_->LookupResource(publicId, resource, type)) 1413 if (!db_.LookupResource(resource, type, publicId))
1122 { 1414 {
1123 throw OrthancException(ErrorCode_UnknownResource); 1415 throw OrthancException(ErrorCode_UnknownResource);
1124 } 1416 }
1125 1417
1126 if (type == ResourceType_Instance) 1418 if (type == ResourceType_Instance)
1128 // An instance cannot have a child 1420 // An instance cannot have a child
1129 throw OrthancException(ErrorCode_BadParameterType); 1421 throw OrthancException(ErrorCode_BadParameterType);
1130 } 1422 }
1131 1423
1132 std::list<int64_t> tmp; 1424 std::list<int64_t> tmp;
1133 db_->GetChildrenInternalId(tmp, resource); 1425 db_.GetChildrenInternalId(tmp, resource);
1134 1426
1135 for (std::list<int64_t>::const_iterator 1427 for (std::list<int64_t>::const_iterator
1136 it = tmp.begin(); it != tmp.end(); ++it) 1428 it = tmp.begin(); it != tmp.end(); ++it)
1137 { 1429 {
1138 result.push_back(db_->GetPublicId(*it)); 1430 result.push_back(db_.GetPublicId(*it));
1139 } 1431 }
1140 } 1432 }
1141 1433
1142 1434
1143 void ServerIndex::GetChildInstances(std::list<std::string>& result, 1435 void ServerIndex::GetChildInstances(std::list<std::string>& result,
1147 1439
1148 boost::mutex::scoped_lock lock(mutex_); 1440 boost::mutex::scoped_lock lock(mutex_);
1149 1441
1150 ResourceType type; 1442 ResourceType type;
1151 int64_t top; 1443 int64_t top;
1152 if (!db_->LookupResource(publicId, top, type)) 1444 if (!db_.LookupResource(top, type, publicId))
1153 { 1445 {
1154 throw OrthancException(ErrorCode_UnknownResource); 1446 throw OrthancException(ErrorCode_UnknownResource);
1155 } 1447 }
1156 1448
1157 if (type == ResourceType_Instance) 1449 if (type == ResourceType_Instance)
1170 { 1462 {
1171 // Get the internal ID of the current resource 1463 // Get the internal ID of the current resource
1172 int64_t resource = toExplore.top(); 1464 int64_t resource = toExplore.top();
1173 toExplore.pop(); 1465 toExplore.pop();
1174 1466
1175 if (db_->GetResourceType(resource) == ResourceType_Instance) 1467 if (db_.GetResourceType(resource) == ResourceType_Instance)
1176 { 1468 {
1177 result.push_back(db_->GetPublicId(resource)); 1469 result.push_back(db_.GetPublicId(resource));
1178 } 1470 }
1179 else 1471 else
1180 { 1472 {
1181 // Tag all the children of this resource as to be explored 1473 // Tag all the children of this resource as to be explored
1182 db_->GetChildrenInternalId(tmp, resource); 1474 db_.GetChildrenInternalId(tmp, resource);
1183 for (std::list<int64_t>::const_iterator 1475 for (std::list<int64_t>::const_iterator
1184 it = tmp.begin(); it != tmp.end(); ++it) 1476 it = tmp.begin(); it != tmp.end(); ++it)
1185 { 1477 {
1186 toExplore.push(*it); 1478 toExplore.push(*it);
1187 } 1479 }
1196 { 1488 {
1197 boost::mutex::scoped_lock lock(mutex_); 1489 boost::mutex::scoped_lock lock(mutex_);
1198 1490
1199 ResourceType rtype; 1491 ResourceType rtype;
1200 int64_t id; 1492 int64_t id;
1201 if (!db_->LookupResource(publicId, id, rtype)) 1493 if (!db_.LookupResource(id, rtype, publicId))
1202 { 1494 {
1203 throw OrthancException(ErrorCode_UnknownResource); 1495 throw OrthancException(ErrorCode_UnknownResource);
1204 } 1496 }
1205 1497
1206 db_->SetMetadata(id, type, value); 1498 db_.SetMetadata(id, type, value);
1207 } 1499 }
1208 1500
1209 1501
1210 void ServerIndex::DeleteMetadata(const std::string& publicId, 1502 void ServerIndex::DeleteMetadata(const std::string& publicId,
1211 MetadataType type) 1503 MetadataType type)
1212 { 1504 {
1213 boost::mutex::scoped_lock lock(mutex_); 1505 boost::mutex::scoped_lock lock(mutex_);
1214 1506
1215 ResourceType rtype; 1507 ResourceType rtype;
1216 int64_t id; 1508 int64_t id;
1217 if (!db_->LookupResource(publicId, id, rtype)) 1509 if (!db_.LookupResource(id, rtype, publicId))
1218 { 1510 {
1219 throw OrthancException(ErrorCode_UnknownResource); 1511 throw OrthancException(ErrorCode_UnknownResource);
1220 } 1512 }
1221 1513
1222 db_->DeleteMetadata(id, type); 1514 db_.DeleteMetadata(id, type);
1223 } 1515 }
1224 1516
1225 1517
1226 bool ServerIndex::LookupMetadata(std::string& target, 1518 bool ServerIndex::LookupMetadata(std::string& target,
1227 const std::string& publicId, 1519 const std::string& publicId,
1229 { 1521 {
1230 boost::mutex::scoped_lock lock(mutex_); 1522 boost::mutex::scoped_lock lock(mutex_);
1231 1523
1232 ResourceType rtype; 1524 ResourceType rtype;
1233 int64_t id; 1525 int64_t id;
1234 if (!db_->LookupResource(publicId, id, rtype)) 1526 if (!db_.LookupResource(id, rtype, publicId))
1235 { 1527 {
1236 throw OrthancException(ErrorCode_UnknownResource); 1528 throw OrthancException(ErrorCode_UnknownResource);
1237 } 1529 }
1238 1530
1239 return db_->LookupMetadata(target, id, type); 1531 return db_.LookupMetadata(target, id, type);
1240 } 1532 }
1241 1533
1242 1534
1243 void ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target, 1535 void ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target,
1244 const std::string& publicId) 1536 const std::string& publicId)
1245 { 1537 {
1246 boost::mutex::scoped_lock lock(mutex_); 1538 boost::mutex::scoped_lock lock(mutex_);
1247 1539
1248 ResourceType rtype; 1540 ResourceType rtype;
1249 int64_t id; 1541 int64_t id;
1250 if (!db_->LookupResource(publicId, id, rtype)) 1542 if (!db_.LookupResource(id, rtype, publicId))
1251 { 1543 {
1252 throw OrthancException(ErrorCode_UnknownResource); 1544 throw OrthancException(ErrorCode_UnknownResource);
1253 } 1545 }
1254 1546
1255 db_->ListAvailableMetadata(target, id); 1547 db_.ListAvailableMetadata(target, id);
1256 } 1548 }
1257 1549
1258 1550
1259 void ServerIndex::ListAvailableAttachments(std::list<FileContentType>& target, 1551 void ServerIndex::ListAvailableAttachments(std::list<FileContentType>& target,
1260 const std::string& publicId, 1552 const std::string& publicId,
1262 { 1554 {
1263 boost::mutex::scoped_lock lock(mutex_); 1555 boost::mutex::scoped_lock lock(mutex_);
1264 1556
1265 ResourceType type; 1557 ResourceType type;
1266 int64_t id; 1558 int64_t id;
1267 if (!db_->LookupResource(publicId, id, type) || 1559 if (!db_.LookupResource(id, type, publicId) ||
1268 expectedType != type) 1560 expectedType != type)
1269 { 1561 {
1270 throw OrthancException(ErrorCode_UnknownResource); 1562 throw OrthancException(ErrorCode_UnknownResource);
1271 } 1563 }
1272 1564
1273 db_->ListAvailableAttachments(target, id); 1565 db_.ListAvailableAttachments(target, id);
1274 } 1566 }
1275 1567
1276 1568
1277 bool ServerIndex::LookupParent(std::string& target, 1569 bool ServerIndex::LookupParent(std::string& target,
1278 const std::string& publicId) 1570 const std::string& publicId)
1279 { 1571 {
1280 boost::mutex::scoped_lock lock(mutex_); 1572 boost::mutex::scoped_lock lock(mutex_);
1281 1573
1282 ResourceType type; 1574 ResourceType type;
1283 int64_t id; 1575 int64_t id;
1284 if (!db_->LookupResource(publicId, id, type)) 1576 if (!db_.LookupResource(id, type, publicId))
1285 { 1577 {
1286 throw OrthancException(ErrorCode_UnknownResource); 1578 throw OrthancException(ErrorCode_UnknownResource);
1287 } 1579 }
1288 1580
1289 int64_t parentId; 1581 int64_t parentId;
1290 if (db_->LookupParent(parentId, id)) 1582 if (db_.LookupParent(parentId, id))
1291 { 1583 {
1292 target = db_->GetPublicId(parentId); 1584 target = db_.GetPublicId(parentId);
1293 return true; 1585 return true;
1294 } 1586 }
1295 else 1587 else
1296 { 1588 {
1297 return false; 1589 return false;
1300 1592
1301 1593
1302 uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence) 1594 uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence)
1303 { 1595 {
1304 boost::mutex::scoped_lock lock(mutex_); 1596 boost::mutex::scoped_lock lock(mutex_);
1305 1597 Transaction transaction(*this);
1306 std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction()); 1598
1307 1599 uint64_t seq = IncrementGlobalSequenceInternal(sequence);
1308 transaction->Begin(); 1600 transaction.Commit(0);
1309 uint64_t seq = db_->IncrementGlobalSequence(sequence);
1310 transaction->Commit();
1311 1601
1312 return seq; 1602 return seq;
1313 } 1603 }
1314 1604
1315 1605
1316 1606
1317 void ServerIndex::LogChange(ChangeType changeType, 1607 void ServerIndex::LogChange(ChangeType changeType,
1318 const std::string& publicId) 1608 const std::string& publicId)
1319 { 1609 {
1320 boost::mutex::scoped_lock lock(mutex_); 1610 boost::mutex::scoped_lock lock(mutex_);
1321 std::auto_ptr<SQLite::Transaction> transaction(db_->StartTransaction()); 1611 Transaction transaction(*this);
1322 transaction->Begin();
1323 1612
1324 int64_t id; 1613 int64_t id;
1325 ResourceType type; 1614 ResourceType type;
1326 if (!db_->LookupResource(publicId, id, type)) 1615 if (!db_.LookupResource(id, type, publicId))
1327 { 1616 {
1328 throw OrthancException(ErrorCode_UnknownResource); 1617 throw OrthancException(ErrorCode_UnknownResource);
1329 } 1618 }
1330 1619
1331 db_->LogChange(changeType, id, type); 1620 LogChange(id, changeType, type, publicId);
1332 1621 transaction.Commit(0);
1333 transaction->Commit();
1334 } 1622 }
1335 1623
1336 1624
1337 void ServerIndex::DeleteChanges() 1625 void ServerIndex::DeleteChanges()
1338 { 1626 {
1339 boost::mutex::scoped_lock lock(mutex_); 1627 boost::mutex::scoped_lock lock(mutex_);
1340 db_->ClearTable("Changes"); 1628 db_.ClearChanges();
1341 } 1629 }
1342 1630
1343 void ServerIndex::DeleteExportedResources() 1631 void ServerIndex::DeleteExportedResources()
1344 { 1632 {
1345 boost::mutex::scoped_lock lock(mutex_); 1633 boost::mutex::scoped_lock lock(mutex_);
1346 db_->ClearTable("ExportedResources"); 1634 db_.ClearExportedResources();
1347 } 1635 }
1348 1636
1349 1637
1350 void ServerIndex::GetStatisticsInternal(/* out */ uint64_t& compressedSize, 1638 void ServerIndex::GetStatisticsInternal(/* out */ uint64_t& compressedSize,
1351 /* out */ uint64_t& uncompressedSize, 1639 /* out */ uint64_t& uncompressedSize,
1368 { 1656 {
1369 // Get the internal ID of the current resource 1657 // Get the internal ID of the current resource
1370 int64_t resource = toExplore.top(); 1658 int64_t resource = toExplore.top();
1371 toExplore.pop(); 1659 toExplore.pop();
1372 1660
1373 ResourceType thisType = db_->GetResourceType(resource); 1661 ResourceType thisType = db_.GetResourceType(resource);
1374 1662
1375 std::list<FileContentType> f; 1663 std::list<FileContentType> f;
1376 db_->ListAvailableAttachments(f, resource); 1664 db_.ListAvailableAttachments(f, resource);
1377 1665
1378 for (std::list<FileContentType>::const_iterator 1666 for (std::list<FileContentType>::const_iterator
1379 it = f.begin(); it != f.end(); ++it) 1667 it = f.begin(); it != f.end(); ++it)
1380 { 1668 {
1381 FileInfo attachment; 1669 FileInfo attachment;
1382 if (db_->LookupAttachment(attachment, resource, *it)) 1670 if (db_.LookupAttachment(attachment, resource, *it))
1383 { 1671 {
1384 compressedSize += attachment.GetCompressedSize(); 1672 compressedSize += attachment.GetCompressedSize();
1385 uncompressedSize += attachment.GetUncompressedSize(); 1673 uncompressedSize += attachment.GetUncompressedSize();
1386 } 1674 }
1387 } 1675 }
1406 break; 1694 break;
1407 } 1695 }
1408 1696
1409 // Tag all the children of this resource as to be explored 1697 // Tag all the children of this resource as to be explored
1410 std::list<int64_t> tmp; 1698 std::list<int64_t> tmp;
1411 db_->GetChildrenInternalId(tmp, resource); 1699 db_.GetChildrenInternalId(tmp, resource);
1412 for (std::list<int64_t>::const_iterator 1700 for (std::list<int64_t>::const_iterator
1413 it = tmp.begin(); it != tmp.end(); ++it) 1701 it = tmp.begin(); it != tmp.end(); ++it)
1414 { 1702 {
1415 toExplore.push(*it); 1703 toExplore.push(*it);
1416 } 1704 }
1435 { 1723 {
1436 boost::mutex::scoped_lock lock(mutex_); 1724 boost::mutex::scoped_lock lock(mutex_);
1437 1725
1438 ResourceType type; 1726 ResourceType type;
1439 int64_t top; 1727 int64_t top;
1440 if (!db_->LookupResource(publicId, top, type)) 1728 if (!db_.LookupResource(top, type, publicId))
1441 { 1729 {
1442 throw OrthancException(ErrorCode_UnknownResource); 1730 throw OrthancException(ErrorCode_UnknownResource);
1443 } 1731 }
1444 1732
1445 uint64_t uncompressedSize; 1733 uint64_t uncompressedSize;
1450 GetStatisticsInternal(compressedSize, uncompressedSize, countStudies, 1738 GetStatisticsInternal(compressedSize, uncompressedSize, countStudies,
1451 countSeries, countInstances, top, type); 1739 countSeries, countInstances, top, type);
1452 1740
1453 target = Json::objectValue; 1741 target = Json::objectValue;
1454 target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize); 1742 target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize);
1455 target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES); 1743 target["DiskSizeMB"] = static_cast<unsigned int>(compressedSize / MEGA_BYTES);
1456 target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); 1744 target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize);
1457 target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES); 1745 target["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
1458 1746
1459 switch (type) 1747 switch (type)
1460 { 1748 {
1461 // Do NOT add "break" below this point! 1749 // Do NOT add "break" below this point!
1462 case ResourceType_Patient: 1750 case ResourceType_Patient:
1484 { 1772 {
1485 boost::mutex::scoped_lock lock(mutex_); 1773 boost::mutex::scoped_lock lock(mutex_);
1486 1774
1487 ResourceType type; 1775 ResourceType type;
1488 int64_t top; 1776 int64_t top;
1489 if (!db_->LookupResource(publicId, top, type)) 1777 if (!db_.LookupResource(top, type, publicId))
1490 { 1778 {
1491 throw OrthancException(ErrorCode_UnknownResource); 1779 throw OrthancException(ErrorCode_UnknownResource);
1492 } 1780 }
1493 1781
1494 GetStatisticsInternal(compressedSize, uncompressedSize, countStudies, 1782 GetStatisticsInternal(compressedSize, uncompressedSize, countStudies,
1521 1809
1522 UnstableResourcePayload payload; 1810 UnstableResourcePayload payload;
1523 int64_t id = that->unstableResources_.RemoveOldest(payload); 1811 int64_t id = that->unstableResources_.RemoveOldest(payload);
1524 1812
1525 // Ensure that the resource is still existing before logging the change 1813 // Ensure that the resource is still existing before logging the change
1526 if (that->db_->IsExistingResource(id)) 1814 if (that->db_.IsExistingResource(id))
1527 { 1815 {
1528 switch (payload.type_) 1816 switch (payload.GetResourceType())
1529 { 1817 {
1530 case Orthanc::ResourceType_Patient: 1818 case ResourceType_Patient:
1531 that->db_->LogChange(ChangeType_StablePatient, id, ResourceType_Patient); 1819 that->LogChange(id, ChangeType_StablePatient, ResourceType_Patient, payload.GetPublicId());
1532 break; 1820 break;
1533 1821
1534 case Orthanc::ResourceType_Study: 1822 case ResourceType_Study:
1535 that->db_->LogChange(ChangeType_StableStudy, id, ResourceType_Study); 1823 that->LogChange(id, ChangeType_StableStudy, ResourceType_Study, payload.GetPublicId());
1536 break; 1824 break;
1537 1825
1538 case Orthanc::ResourceType_Series: 1826 case ResourceType_Series:
1539 that->db_->LogChange(ChangeType_StableSeries, id, ResourceType_Series); 1827 that->LogChange(id, ChangeType_StableSeries, ResourceType_Series, payload.GetPublicId());
1540 break; 1828 break;
1541 1829
1542 default: 1830 default:
1543 throw OrthancException(ErrorCode_InternalError); 1831 throw OrthancException(ErrorCode_InternalError);
1544 } 1832 }
1551 LOG(INFO) << "Closing the monitor thread for stable resources"; 1839 LOG(INFO) << "Closing the monitor thread for stable resources";
1552 } 1840 }
1553 1841
1554 1842
1555 void ServerIndex::MarkAsUnstable(int64_t id, 1843 void ServerIndex::MarkAsUnstable(int64_t id,
1556 Orthanc::ResourceType type) 1844 Orthanc::ResourceType type,
1845 const std::string& publicId)
1557 { 1846 {
1558 // WARNING: Before calling this method, "mutex_" must be locked. 1847 // WARNING: Before calling this method, "mutex_" must be locked.
1559 1848
1560 assert(type == Orthanc::ResourceType_Patient || 1849 assert(type == Orthanc::ResourceType_Patient ||
1561 type == Orthanc::ResourceType_Study || 1850 type == Orthanc::ResourceType_Study ||
1562 type == Orthanc::ResourceType_Series); 1851 type == Orthanc::ResourceType_Series);
1563 1852
1564 unstableResources_.AddOrMakeMostRecent(id, type); 1853 UnstableResourcePayload payload(type, publicId);
1854 unstableResources_.AddOrMakeMostRecent(id, payload);
1565 //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id; 1855 //LOG(INFO) << "Unstable resource: " << EnumerationToString(type) << " " << id;
1566 } 1856
1567 1857 LogChange(id, ChangeType_NewChildInstance, type, publicId);
1568 1858 }
1569 1859
1570 void ServerIndex::LookupTagValue(std::list<std::string>& result, 1860
1571 DicomTag tag, 1861
1572 const std::string& value, 1862 void ServerIndex::LookupIdentifier(std::list<std::string>& result,
1573 ResourceType type) 1863 const DicomTag& tag,
1864 const std::string& value,
1865 ResourceType type)
1574 { 1866 {
1575 result.clear(); 1867 result.clear();
1576 1868
1577 boost::mutex::scoped_lock lock(mutex_); 1869 boost::mutex::scoped_lock lock(mutex_);
1578 1870
1579 std::list<int64_t> id; 1871 std::list<int64_t> id;
1580 db_->LookupTagValue(id, tag, value); 1872 db_.LookupIdentifier(id, tag, value);
1581 1873
1582 for (std::list<int64_t>::const_iterator 1874 for (std::list<int64_t>::const_iterator
1583 it = id.begin(); it != id.end(); ++it) 1875 it = id.begin(); it != id.end(); ++it)
1584 { 1876 {
1585 if (db_->GetResourceType(*it) == type) 1877 if (db_.GetResourceType(*it) == type)
1586 { 1878 {
1587 result.push_back(db_->GetPublicId(*it)); 1879 result.push_back(db_.GetPublicId(*it));
1588 } 1880 }
1589 } 1881 }
1590 } 1882 }
1591 1883
1592 1884
1593 void ServerIndex::LookupTagValue(std::list<std::string>& result, 1885 void ServerIndex::LookupIdentifier(std::list<std::string>& result,
1594 DicomTag tag, 1886 const DicomTag& tag,
1595 const std::string& value) 1887 const std::string& value)
1596 { 1888 {
1597 result.clear(); 1889 result.clear();
1598 1890
1599 boost::mutex::scoped_lock lock(mutex_); 1891 boost::mutex::scoped_lock lock(mutex_);
1600 1892
1601 std::list<int64_t> id; 1893 std::list<int64_t> id;
1602 db_->LookupTagValue(id, tag, value); 1894 db_.LookupIdentifier(id, tag, value);
1603 1895
1604 for (std::list<int64_t>::const_iterator 1896 for (std::list<int64_t>::const_iterator
1605 it = id.begin(); it != id.end(); ++it) 1897 it = id.begin(); it != id.end(); ++it)
1606 { 1898 {
1607 result.push_back(db_->GetPublicId(*it)); 1899 result.push_back(db_.GetPublicId(*it));
1608 } 1900 }
1609 } 1901 }
1610 1902
1611 1903
1612 void ServerIndex::LookupTagValue(std::list<std::string>& result, 1904 void ServerIndex::LookupIdentifier(std::list< std::pair<ResourceType, std::string> >& result,
1613 const std::string& value) 1905 const std::string& value)
1614 { 1906 {
1615 result.clear(); 1907 result.clear();
1616 1908
1617 boost::mutex::scoped_lock lock(mutex_); 1909 boost::mutex::scoped_lock lock(mutex_);
1618 1910
1619 std::list<int64_t> id; 1911 std::list<int64_t> id;
1620 db_->LookupTagValue(id, value); 1912 db_.LookupIdentifier(id, value);
1621 1913
1622 for (std::list<int64_t>::const_iterator 1914 for (std::list<int64_t>::const_iterator
1623 it = id.begin(); it != id.end(); ++it) 1915 it = id.begin(); it != id.end(); ++it)
1624 { 1916 {
1625 result.push_back(db_->GetPublicId(*it)); 1917 result.push_back(std::make_pair(db_.GetResourceType(*it),
1918 db_.GetPublicId(*it)));
1626 } 1919 }
1627 } 1920 }
1628 1921
1629 1922
1630 StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment, 1923 StoreStatus ServerIndex::AddAttachment(const FileInfo& attachment,
1634 1927
1635 Transaction t(*this); 1928 Transaction t(*this);
1636 1929
1637 ResourceType resourceType; 1930 ResourceType resourceType;
1638 int64_t resourceId; 1931 int64_t resourceId;
1639 if (!db_->LookupResource(publicId, resourceId, resourceType)) 1932 if (!db_.LookupResource(resourceId, resourceType, publicId))
1640 { 1933 {
1641 return StoreStatus_Failure; // Inexistent resource 1934 return StoreStatus_Failure; // Inexistent resource
1642 } 1935 }
1643 1936
1644 // Remove possible previous attachment 1937 // Remove possible previous attachment
1645 db_->DeleteAttachment(resourceId, attachment.GetContentType()); 1938 db_.DeleteAttachment(resourceId, attachment.GetContentType());
1646 1939
1647 // Locate the patient of the target resource 1940 // Locate the patient of the target resource
1648 int64_t patientId = resourceId; 1941 int64_t patientId = resourceId;
1649 for (;;) 1942 for (;;)
1650 { 1943 {
1651 int64_t parent; 1944 int64_t parent;
1652 if (db_->LookupParent(parent, patientId)) 1945 if (db_.LookupParent(parent, patientId))
1653 { 1946 {
1654 // We have not reached the patient level yet 1947 // We have not reached the patient level yet
1655 patientId = parent; 1948 patientId = parent;
1656 } 1949 }
1657 else 1950 else
1660 break; 1953 break;
1661 } 1954 }
1662 } 1955 }
1663 1956
1664 // Possibly apply the recycling mechanism while preserving this patient 1957 // Possibly apply the recycling mechanism while preserving this patient
1665 assert(db_->GetResourceType(patientId) == ResourceType_Patient); 1958 assert(db_.GetResourceType(patientId) == ResourceType_Patient);
1666 Recycle(attachment.GetCompressedSize(), db_->GetPublicId(patientId)); 1959 Recycle(attachment.GetCompressedSize(), db_.GetPublicId(patientId));
1667 1960
1668 db_->AddAttachment(resourceId, attachment); 1961 db_.AddAttachment(resourceId, attachment);
1669 1962
1670 t.Commit(attachment.GetCompressedSize()); 1963 t.Commit(attachment.GetCompressedSize());
1671 1964
1672 return StoreStatus_Success; 1965 return StoreStatus_Success;
1673 } 1966 }
1675 1968
1676 void ServerIndex::DeleteAttachment(const std::string& publicId, 1969 void ServerIndex::DeleteAttachment(const std::string& publicId,
1677 FileContentType type) 1970 FileContentType type)
1678 { 1971 {
1679 boost::mutex::scoped_lock lock(mutex_); 1972 boost::mutex::scoped_lock lock(mutex_);
1680 listener_->Reset();
1681
1682 Transaction t(*this); 1973 Transaction t(*this);
1683 1974
1684 ResourceType rtype; 1975 ResourceType rtype;
1685 int64_t id; 1976 int64_t id;
1686 if (!db_->LookupResource(publicId, id, rtype)) 1977 if (!db_.LookupResource(id, rtype, publicId))
1687 { 1978 {
1688 throw OrthancException(ErrorCode_UnknownResource); 1979 throw OrthancException(ErrorCode_UnknownResource);
1689 } 1980 }
1690 1981
1691 db_->DeleteAttachment(id, type); 1982 db_.DeleteAttachment(id, type);
1692 1983
1693 t.Commit(0); 1984 t.Commit(0);
1694 } 1985 }
1695 1986
1696 1987
1988 bool ServerIndex::GetMetadata(Json::Value& target,
1989 const std::string& publicId)
1990 {
1991 boost::mutex::scoped_lock lock(mutex_);
1992
1993 target = Json::objectValue;
1994
1995 ResourceType type;
1996 int64_t id;
1997 if (!db_.LookupResource(id, type, publicId))
1998 {
1999 return false;
2000 }
2001
2002 std::list<MetadataType> metadata;
2003 db_.ListAvailableMetadata(metadata, id);
2004
2005 for (std::list<MetadataType>::const_iterator
2006 it = metadata.begin(); it != metadata.end(); ++it)
2007 {
2008 std::string key = EnumerationToString(*it);
2009
2010 std::string value;
2011 if (!db_.LookupMetadata(value, id, *it))
2012 {
2013 value.clear();
2014 }
2015
2016 target[key] = value;
2017 }
2018
2019 return true;
2020 }
2021
2022
2023 void ServerIndex::SetGlobalProperty(GlobalProperty property,
2024 const std::string& value)
2025 {
2026 boost::mutex::scoped_lock lock(mutex_);
2027 db_.SetGlobalProperty(property, value);
2028 }
2029
2030
2031 std::string ServerIndex::GetGlobalProperty(GlobalProperty property,
2032 const std::string& defaultValue)
2033 {
2034 boost::mutex::scoped_lock lock(mutex_);
2035
2036 std::string value;
2037 if (db_.LookupGlobalProperty(value, property))
2038 {
2039 return value;
2040 }
2041 else
2042 {
2043 return defaultValue;
2044 }
2045 }
2046
2047
2048 bool ServerIndex::GetMainDicomTags(DicomMap& result,
2049 const std::string& publicId,
2050 ResourceType expectedType)
2051 {
2052 result.Clear();
2053
2054 boost::mutex::scoped_lock lock(mutex_);
2055
2056 // Lookup for the requested resource
2057 int64_t id;
2058 ResourceType type;
2059 if (!db_.LookupResource(id, type, publicId) ||
2060 type != expectedType)
2061 {
2062 return false;
2063 }
2064 else
2065 {
2066 db_.GetMainDicomTags(result, id);
2067 return true;
2068 }
2069 }
1697 } 2070 }