comparison OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp @ 5807:8279eaab0d1d attach-custom-data

merged default -> attach-custom-data
author Alain Mazy <am@orthanc.team>
date Tue, 24 Sep 2024 11:39:52 +0200
parents 75e949689c08 68fc5af30c03
children 023a99146dd0
comparison
equal deleted inserted replaced
5085:79f98ee4f04b 5807:8279eaab0d1d
1 /** 1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store 2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium 4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium 5 * Copyright (C) 2017-2023 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium 6 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
7 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 * 8 *
8 * This program is free software: you can redistribute it and/or 9 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as 10 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation, either version 3 of the 11 * published by the Free Software Foundation, either version 3 of the
11 * License, or (at your option) any later version. 12 * License, or (at your option) any later version.
25 26
26 #if ORTHANC_ENABLE_PLUGINS != 1 27 #if ORTHANC_ENABLE_PLUGINS != 1
27 # error The plugin support is disabled 28 # error The plugin support is disabled
28 #endif 29 #endif
29 30
31 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
30 #include "../../../OrthancFramework/Sources/Logging.h" 32 #include "../../../OrthancFramework/Sources/Logging.h"
31 #include "../../../OrthancFramework/Sources/OrthancException.h" 33 #include "../../../OrthancFramework/Sources/OrthancException.h"
32 #include "../../Sources/Database/ResourcesContent.h" 34 #include "../../Sources/Database/ResourcesContent.h"
33 #include "../../Sources/Database/VoidDatabaseListener.h" 35 #include "../../Sources/Database/VoidDatabaseListener.h"
36 #include "../../Sources/ServerToolbox.h"
34 #include "PluginsEnumerations.h" 37 #include "PluginsEnumerations.h"
35 38
39 #include "OrthancDatabasePlugin.pb.h" // Auto-generated file
40
36 #include <cassert> 41 #include <cassert>
37 42
38
39 #define CHECK_FUNCTION_EXISTS(backend, func) \
40 if (backend.func == NULL) \
41 { \
42 throw OrthancException( \
43 ErrorCode_DatabasePlugin, "Missing primitive: " #func "()"); \
44 }
45 43
46 namespace Orthanc 44 namespace Orthanc
47 { 45 {
46 static void CheckSuccess(PluginsErrorDictionary& errorDictionary,
47 OrthancPluginErrorCode code)
48 {
49 if (code != OrthancPluginErrorCode_Success)
50 {
51 errorDictionary.LogError(code, true);
52 throw OrthancException(static_cast<ErrorCode>(code));
53 }
54 }
55
56
57 static ResourceType Convert(DatabasePluginMessages::ResourceType type)
58 {
59 switch (type)
60 {
61 case DatabasePluginMessages::RESOURCE_PATIENT:
62 return ResourceType_Patient;
63
64 case DatabasePluginMessages::RESOURCE_STUDY:
65 return ResourceType_Study;
66
67 case DatabasePluginMessages::RESOURCE_SERIES:
68 return ResourceType_Series;
69
70 case DatabasePluginMessages::RESOURCE_INSTANCE:
71 return ResourceType_Instance;
72
73 default:
74 throw OrthancException(ErrorCode_ParameterOutOfRange);
75 }
76 }
77
78
79 static DatabasePluginMessages::ResourceType Convert(ResourceType type)
80 {
81 switch (type)
82 {
83 case ResourceType_Patient:
84 return DatabasePluginMessages::RESOURCE_PATIENT;
85
86 case ResourceType_Study:
87 return DatabasePluginMessages::RESOURCE_STUDY;
88
89 case ResourceType_Series:
90 return DatabasePluginMessages::RESOURCE_SERIES;
91
92 case ResourceType_Instance:
93 return DatabasePluginMessages::RESOURCE_INSTANCE;
94
95 default:
96 throw OrthancException(ErrorCode_ParameterOutOfRange);
97 }
98 }
99
100
101 static FileInfo Convert(const DatabasePluginMessages::FileInfo& source)
102 {
103 return FileInfo(source.uuid(),
104 static_cast<FileContentType>(source.content_type()),
105 source.uncompressed_size(),
106 source.uncompressed_hash(),
107 static_cast<CompressionType>(source.compression_type()),
108 source.compressed_size(),
109 source.compressed_hash(),
110 source.custom_data());
111 }
112
113
114 static ServerIndexChange Convert(const DatabasePluginMessages::ServerIndexChange& source)
115 {
116 return ServerIndexChange(source.seq(),
117 static_cast<ChangeType>(source.change_type()),
118 Convert(source.resource_type()),
119 source.public_id(),
120 source.date());
121 }
122
123
124 static ExportedResource Convert(const DatabasePluginMessages::ExportedResource& source)
125 {
126 return ExportedResource(source.seq(),
127 Convert(source.resource_type()),
128 source.public_id(),
129 source.modality(),
130 source.date(),
131 source.patient_id(),
132 source.study_instance_uid(),
133 source.series_instance_uid(),
134 source.sop_instance_uid());
135 }
136
137
138 static void Execute(DatabasePluginMessages::Response& response,
139 const OrthancPluginDatabaseV4& database,
140 const DatabasePluginMessages::Request& request)
141 {
142 std::string requestSerialized;
143 request.SerializeToString(&requestSerialized);
144
145 OrthancPluginMemoryBuffer64 responseSerialized;
146 CheckSuccess(database.GetErrorDictionary(), database.GetDefinition().operations(
147 &responseSerialized, database.GetDefinition().backend,
148 requestSerialized.empty() ? NULL : requestSerialized.c_str(),
149 requestSerialized.size()));
150
151 bool success = response.ParseFromArray(responseSerialized.data, responseSerialized.size);
152
153 if (responseSerialized.size > 0)
154 {
155 free(responseSerialized.data);
156 }
157
158 if (!success)
159 {
160 throw OrthancException(ErrorCode_DatabasePlugin, "Cannot unserialize protobuf originating from the database plugin");
161 }
162 }
163
164
165 static void ExecuteDatabase(DatabasePluginMessages::DatabaseResponse& response,
166 const OrthancPluginDatabaseV4& database,
167 DatabasePluginMessages::DatabaseOperation operation,
168 const DatabasePluginMessages::DatabaseRequest& request)
169 {
170 DatabasePluginMessages::Request fullRequest;
171 fullRequest.set_type(DatabasePluginMessages::REQUEST_DATABASE);
172 fullRequest.mutable_database_request()->CopyFrom(request);
173 fullRequest.mutable_database_request()->set_operation(operation);
174
175 DatabasePluginMessages::Response fullResponse;
176 Execute(fullResponse, database, fullRequest);
177
178 response.CopyFrom(fullResponse.database_response());
179 }
180
181
48 class OrthancPluginDatabaseV4::Transaction : public IDatabaseWrapper::ITransaction 182 class OrthancPluginDatabaseV4::Transaction : public IDatabaseWrapper::ITransaction
49 { 183 {
50 private: 184 private:
51 OrthancPluginDatabaseV4& that_; 185 OrthancPluginDatabaseV4& database_;
52 IDatabaseListener& listener_; 186 IDatabaseListener& listener_;
53 OrthancPluginDatabaseTransaction* transaction_; 187 void* transaction_;
54 188
55 189 void ExecuteTransaction(DatabasePluginMessages::TransactionResponse& response,
56 void CheckSuccess(OrthancPluginErrorCode code) const 190 DatabasePluginMessages::TransactionOperation operation,
57 { 191 const DatabasePluginMessages::TransactionRequest& request)
58 that_.CheckSuccess(code); 192 {
59 } 193 DatabasePluginMessages::Request fullRequest;
60 194 fullRequest.set_type(DatabasePluginMessages::REQUEST_TRANSACTION);
61 195 fullRequest.mutable_transaction_request()->CopyFrom(request);
62 static FileInfo Convert(const OrthancPluginAttachment& attachment) 196 fullRequest.mutable_transaction_request()->set_transaction(reinterpret_cast<intptr_t>(transaction_));
63 { 197 fullRequest.mutable_transaction_request()->set_operation(operation);
64 std::string customData; 198
65 return FileInfo(attachment.uuid, 199 DatabasePluginMessages::Response fullResponse;
66 static_cast<FileContentType>(attachment.contentType), 200 Execute(fullResponse, database_, fullRequest);
67 attachment.uncompressedSize, 201
68 attachment.uncompressedHash, 202 response.CopyFrom(fullResponse.transaction_response());
69 static_cast<CompressionType>(attachment.compressionType), 203 }
70 attachment.compressedSize, 204
71 attachment.compressedHash, 205
72 customData); 206 void ExecuteTransaction(DatabasePluginMessages::TransactionResponse& response,
73 } 207 DatabasePluginMessages::TransactionOperation operation)
74 208 {
75 static FileInfo Convert(const OrthancPluginAttachment2& attachment) 209 DatabasePluginMessages::TransactionRequest request; // Ignored
76 { 210 ExecuteTransaction(response, operation, request);
77 return FileInfo(attachment.uuid, 211 }
78 static_cast<FileContentType>(attachment.contentType), 212
79 attachment.uncompressedSize, 213
80 attachment.uncompressedHash, 214 void ExecuteTransaction(DatabasePluginMessages::TransactionOperation operation,
81 static_cast<CompressionType>(attachment.compressionType), 215 const DatabasePluginMessages::TransactionRequest& request)
82 attachment.compressedSize, 216 {
83 attachment.compressedHash, 217 DatabasePluginMessages::TransactionResponse response; // Ignored
84 attachment.customData); 218 ExecuteTransaction(response, operation, request);
85 } 219 }
86 220
87 void ReadStringAnswers(std::list<std::string>& target) 221
88 { 222 void ExecuteTransaction(DatabasePluginMessages::TransactionOperation operation)
89 uint32_t count; 223 {
90 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count)); 224 DatabasePluginMessages::TransactionResponse response; // Ignored
225 DatabasePluginMessages::TransactionRequest request; // Ignored
226 ExecuteTransaction(response, operation, request);
227 }
228
229
230 void ListLabelsInternal(std::set<std::string>& target,
231 bool isSingleResource,
232 int64_t resource)
233 {
234 if (database_.GetDatabaseCapabilities().HasLabelsSupport())
235 {
236 DatabasePluginMessages::TransactionRequest request;
237 request.mutable_list_labels()->set_single_resource(isSingleResource);
238 request.mutable_list_labels()->set_id(resource);
239
240 DatabasePluginMessages::TransactionResponse response;
241 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LIST_LABELS, request);
242
243 target.clear();
244 for (int i = 0; i < response.list_labels().labels().size(); i++)
245 {
246 target.insert(response.list_labels().labels(i));
247 }
248 }
249 else
250 {
251 // This method shouldn't have been called
252 throw OrthancException(ErrorCode_InternalError);
253 }
254 }
255
256
257 public:
258 Transaction(OrthancPluginDatabaseV4& database,
259 IDatabaseListener& listener,
260 TransactionType type) :
261 database_(database),
262 listener_(listener),
263 transaction_(NULL)
264 {
265 DatabasePluginMessages::DatabaseRequest request;
266
267 switch (type)
268 {
269 case TransactionType_ReadOnly:
270 request.mutable_start_transaction()->set_type(DatabasePluginMessages::TRANSACTION_READ_ONLY);
271 break;
272
273 case TransactionType_ReadWrite:
274 request.mutable_start_transaction()->set_type(DatabasePluginMessages::TRANSACTION_READ_WRITE);
275 break;
276
277 default:
278 throw OrthancException(ErrorCode_ParameterOutOfRange);
279 }
280
281 DatabasePluginMessages::DatabaseResponse response;
282 ExecuteDatabase(response, database, DatabasePluginMessages::OPERATION_START_TRANSACTION, request);
283
284 transaction_ = reinterpret_cast<void*>(response.start_transaction().transaction());
285
286 if (transaction_ == NULL)
287 {
288 throw OrthancException(ErrorCode_NullPointer);
289 }
290 }
291
292
293 virtual ~Transaction()
294 {
295 try
296 {
297 DatabasePluginMessages::DatabaseRequest request;
298 request.mutable_finalize_transaction()->set_transaction(reinterpret_cast<intptr_t>(transaction_));
299
300 DatabasePluginMessages::DatabaseResponse response;
301 ExecuteDatabase(response, database_, DatabasePluginMessages::OPERATION_FINALIZE_TRANSACTION, request);
302 }
303 catch (OrthancException& e)
304 {
305 // Destructors must not throw exceptions
306 LOG(ERROR) << "Cannot finalize the database engine: " << e.What();
307 }
308 }
309
310 void* GetTransactionObject()
311 {
312 return transaction_;
313 }
314
315
316 virtual void Rollback() ORTHANC_OVERRIDE
317 {
318 ExecuteTransaction(DatabasePluginMessages::OPERATION_ROLLBACK);
319 }
320
321
322 virtual void Commit(int64_t fileSizeDelta) ORTHANC_OVERRIDE
323 {
324 DatabasePluginMessages::TransactionRequest request;
325 request.mutable_commit()->set_file_size_delta(fileSizeDelta);
326
327 ExecuteTransaction(DatabasePluginMessages::OPERATION_COMMIT, request);
328 }
329
330
331 virtual void AddAttachment(int64_t id,
332 const FileInfo& attachment,
333 int64_t revision) ORTHANC_OVERRIDE
334 {
335 DatabasePluginMessages::TransactionRequest request;
336 request.mutable_add_attachment()->set_id(id);
337 request.mutable_add_attachment()->mutable_attachment()->set_uuid(attachment.GetUuid());
338 request.mutable_add_attachment()->mutable_attachment()->set_content_type(attachment.GetContentType());
339 request.mutable_add_attachment()->mutable_attachment()->set_uncompressed_size(attachment.GetUncompressedSize());
340 request.mutable_add_attachment()->mutable_attachment()->set_uncompressed_hash(attachment.GetUncompressedMD5());
341 request.mutable_add_attachment()->mutable_attachment()->set_compression_type(attachment.GetCompressionType());
342 request.mutable_add_attachment()->mutable_attachment()->set_compressed_size(attachment.GetCompressedSize());
343 request.mutable_add_attachment()->mutable_attachment()->set_compressed_hash(attachment.GetCompressedMD5());
344 request.mutable_add_attachment()->set_revision(revision);
345
346 ExecuteTransaction(DatabasePluginMessages::OPERATION_ADD_ATTACHMENT, request);
347 }
348
349
350 virtual void ClearChanges() ORTHANC_OVERRIDE
351 {
352 ExecuteTransaction(DatabasePluginMessages::OPERATION_CLEAR_CHANGES);
353 }
354
355
356 virtual void ClearExportedResources() ORTHANC_OVERRIDE
357 {
358 ExecuteTransaction(DatabasePluginMessages::OPERATION_CLEAR_EXPORTED_RESOURCES);
359 }
360
361
362 virtual void DeleteAttachment(int64_t id,
363 FileContentType attachment) ORTHANC_OVERRIDE
364 {
365 DatabasePluginMessages::TransactionRequest request;
366 request.mutable_delete_attachment()->set_id(id);
367 request.mutable_delete_attachment()->set_type(attachment);
368
369 DatabasePluginMessages::TransactionResponse response;
370 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_DELETE_ATTACHMENT, request);
371
372 listener_.SignalAttachmentDeleted(Convert(response.delete_attachment().deleted_attachment()));
373 }
374
375
376 virtual void DeleteMetadata(int64_t id,
377 MetadataType type) ORTHANC_OVERRIDE
378 {
379 DatabasePluginMessages::TransactionRequest request;
380 request.mutable_delete_metadata()->set_id(id);
381 request.mutable_delete_metadata()->set_type(type);
382
383 ExecuteTransaction(DatabasePluginMessages::OPERATION_DELETE_METADATA, request);
384 }
385
386
387 virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
388 {
389 DatabasePluginMessages::TransactionRequest request;
390 request.mutable_delete_resource()->set_id(id);
391
392 DatabasePluginMessages::TransactionResponse response;
393 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_DELETE_RESOURCE, request);
394
395 for (int i = 0; i < response.delete_resource().deleted_attachments().size(); i++)
396 {
397 listener_.SignalAttachmentDeleted(Convert(response.delete_resource().deleted_attachments(i)));
398 }
399
400 for (int i = 0; i < response.delete_resource().deleted_resources().size(); i++)
401 {
402 listener_.SignalResourceDeleted(Convert(response.delete_resource().deleted_resources(i).level()),
403 response.delete_resource().deleted_resources(i).public_id());
404 }
405
406 if (response.delete_resource().is_remaining_ancestor())
407 {
408 listener_.SignalRemainingAncestor(Convert(response.delete_resource().remaining_ancestor().level()),
409 response.delete_resource().remaining_ancestor().public_id());
410 }
411 }
412
413
414 virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
415 int64_t id) ORTHANC_OVERRIDE
416 {
417 DatabasePluginMessages::TransactionRequest request;
418 request.mutable_get_all_metadata()->set_id(id);
419
420 DatabasePluginMessages::TransactionResponse response;
421 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_ALL_METADATA, request);
91 422
92 target.clear(); 423 target.clear();
93 for (uint32_t i = 0; i < count; i++) 424 for (int i = 0; i < response.get_all_metadata().metadata().size(); i++)
94 { 425 {
95 const char* value = NULL; 426 MetadataType key = static_cast<MetadataType>(response.get_all_metadata().metadata(i).type());
96 CheckSuccess(that_.backend_.readAnswerString(transaction_, &value, i)); 427
97 if (value == NULL) 428 if (target.find(key) == target.end())
429 {
430 target[key] = response.get_all_metadata().metadata(i).value();
431 }
432 else
433 {
434 throw OrthancException(ErrorCode_DatabasePlugin);
435 }
436 }
437 }
438
439
440 virtual void GetAllPublicIds(std::list<std::string>& target,
441 ResourceType resourceType) ORTHANC_OVERRIDE
442 {
443 DatabasePluginMessages::TransactionRequest request;
444 request.mutable_get_all_public_ids()->set_resource_type(Convert(resourceType));
445
446 DatabasePluginMessages::TransactionResponse response;
447 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS, request);
448
449 target.clear();
450 for (int i = 0; i < response.get_all_public_ids().ids().size(); i++)
451 {
452 target.push_back(response.get_all_public_ids().ids(i));
453 }
454 }
455
456
457 virtual void GetAllPublicIds(std::list<std::string>& target,
458 ResourceType resourceType,
459 int64_t since,
460 uint32_t limit) ORTHANC_OVERRIDE
461 {
462 DatabasePluginMessages::TransactionRequest request;
463 request.mutable_get_all_public_ids_with_limits()->set_resource_type(Convert(resourceType));
464 request.mutable_get_all_public_ids_with_limits()->set_since(since);
465 request.mutable_get_all_public_ids_with_limits()->set_limit(limit);
466
467 DatabasePluginMessages::TransactionResponse response;
468 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS, request);
469
470 target.clear();
471 for (int i = 0; i < response.get_all_public_ids_with_limits().ids().size(); i++)
472 {
473 target.push_back(response.get_all_public_ids_with_limits().ids(i));
474 }
475 }
476
477
478 virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
479 bool& done /*out*/,
480 int64_t since,
481 uint32_t limit) ORTHANC_OVERRIDE
482 {
483 DatabasePluginMessages::TransactionRequest request;
484 request.mutable_get_changes()->set_since(since);
485 request.mutable_get_changes()->set_limit(limit);
486
487 DatabasePluginMessages::TransactionResponse response;
488 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_CHANGES, request);
489
490 done = response.get_changes().done();
491
492 target.clear();
493 for (int i = 0; i < response.get_changes().changes().size(); i++)
494 {
495 target.push_back(Convert(response.get_changes().changes(i)));
496 }
497 }
498
499
500 virtual void GetChildrenInternalId(std::list<int64_t>& target,
501 int64_t id) ORTHANC_OVERRIDE
502 {
503 DatabasePluginMessages::TransactionRequest request;
504 request.mutable_get_children_internal_id()->set_id(id);
505
506 DatabasePluginMessages::TransactionResponse response;
507 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_CHILDREN_INTERNAL_ID, request);
508
509 target.clear();
510 for (int i = 0; i < response.get_children_internal_id().ids().size(); i++)
511 {
512 target.push_back(response.get_children_internal_id().ids(i));
513 }
514 }
515
516
517 virtual void GetChildrenPublicId(std::list<std::string>& target,
518 int64_t id) ORTHANC_OVERRIDE
519 {
520 DatabasePluginMessages::TransactionRequest request;
521 request.mutable_get_children_public_id()->set_id(id);
522
523 DatabasePluginMessages::TransactionResponse response;
524 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_CHILDREN_PUBLIC_ID, request);
525
526 target.clear();
527 for (int i = 0; i < response.get_children_public_id().ids().size(); i++)
528 {
529 target.push_back(response.get_children_public_id().ids(i));
530 }
531 }
532
533
534 virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
535 bool& done /*out*/,
536 int64_t since,
537 uint32_t limit) ORTHANC_OVERRIDE
538 {
539 DatabasePluginMessages::TransactionRequest request;
540 request.mutable_get_exported_resources()->set_since(since);
541 request.mutable_get_exported_resources()->set_limit(limit);
542
543 DatabasePluginMessages::TransactionResponse response;
544 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_EXPORTED_RESOURCES, request);
545
546 done = response.get_exported_resources().done();
547
548 target.clear();
549 for (int i = 0; i < response.get_exported_resources().resources().size(); i++)
550 {
551 target.push_back(Convert(response.get_exported_resources().resources(i)));
552 }
553 }
554
555
556 virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
557 {
558 DatabasePluginMessages::TransactionResponse response;
559 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_LAST_CHANGE);
560
561 target.clear();
562 if (response.get_last_change().found())
563 {
564 target.push_back(Convert(response.get_last_change().change()));
565 }
566 }
567
568
569 virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
570 {
571 DatabasePluginMessages::TransactionResponse response;
572 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_LAST_EXPORTED_RESOURCE);
573
574 target.clear();
575 if (response.get_last_exported_resource().found())
576 {
577 target.push_back(Convert(response.get_last_exported_resource().resource()));
578 }
579 }
580
581
582 virtual void GetMainDicomTags(DicomMap& target,
583 int64_t id) ORTHANC_OVERRIDE
584 {
585 DatabasePluginMessages::TransactionRequest request;
586 request.mutable_get_main_dicom_tags()->set_id(id);
587
588 DatabasePluginMessages::TransactionResponse response;
589 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_MAIN_DICOM_TAGS, request);
590
591 target.Clear();
592
593 for (int i = 0; i < response.get_main_dicom_tags().tags().size(); i++)
594 {
595 const DatabasePluginMessages::GetMainDicomTags_Response_Tag& tag = response.get_main_dicom_tags().tags(i);
596 if (tag.group() > 0xffffu ||
597 tag.element() > 0xffffu)
598 {
599 throw OrthancException(ErrorCode_ParameterOutOfRange);
600 }
601 else
602 {
603 target.SetValue(tag.group(), tag.element(), tag.value(), false);
604 }
605 }
606 }
607
608
609 virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
610 {
611 DatabasePluginMessages::TransactionRequest request;
612 request.mutable_get_public_id()->set_id(resourceId);
613
614 DatabasePluginMessages::TransactionResponse response;
615 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_PUBLIC_ID, request);
616 return response.get_public_id().id();
617 }
618
619
620 virtual uint64_t GetResourcesCount(ResourceType resourceType) ORTHANC_OVERRIDE
621 {
622 DatabasePluginMessages::TransactionRequest request;
623 request.mutable_get_resources_count()->set_type(Convert(resourceType));
624
625 DatabasePluginMessages::TransactionResponse response;
626 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_RESOURCES_COUNT, request);
627 return response.get_resources_count().count();
628 }
629
630
631 virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
632 {
633 DatabasePluginMessages::TransactionRequest request;
634 request.mutable_get_resource_type()->set_id(resourceId);
635
636 DatabasePluginMessages::TransactionResponse response;
637 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_RESOURCE_TYPE, request);
638 return Convert(response.get_resource_type().type());
639 }
640
641
642 virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
643 {
644 DatabasePluginMessages::TransactionResponse response;
645 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_TOTAL_COMPRESSED_SIZE);
646 return response.get_total_compressed_size().size();
647 }
648
649
650 virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
651 {
652 DatabasePluginMessages::TransactionResponse response;
653 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE);
654 return response.get_total_uncompressed_size().size();
655 }
656
657
658 virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
659 {
660 DatabasePluginMessages::TransactionRequest request;
661 request.mutable_is_protected_patient()->set_patient_id(internalId);
662
663 DatabasePluginMessages::TransactionResponse response;
664 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_IS_PROTECTED_PATIENT, request);
665 return response.is_protected_patient().protected_patient();
666 }
667
668
669 virtual void ListAvailableAttachments(std::set<FileContentType>& target,
670 int64_t id) ORTHANC_OVERRIDE
671 {
672 DatabasePluginMessages::TransactionRequest request;
673 request.mutable_list_available_attachments()->set_id(id);
674
675 DatabasePluginMessages::TransactionResponse response;
676 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LIST_AVAILABLE_ATTACHMENTS, request);
677
678 target.clear();
679 for (int i = 0; i < response.list_available_attachments().attachments().size(); i++)
680 {
681 FileContentType attachment = static_cast<FileContentType>(response.list_available_attachments().attachments(i));
682
683 if (target.find(attachment) == target.end())
684 {
685 target.insert(attachment);
686 }
687 else
688 {
689 throw OrthancException(ErrorCode_DatabasePlugin);
690 }
691 }
692 }
693
694
695 virtual void LogChange(ChangeType changeType,
696 ResourceType resourceType,
697 int64_t internalId,
698 const std::string& /* publicId - unused */,
699 const std::string& date) ORTHANC_OVERRIDE
700 {
701 DatabasePluginMessages::TransactionRequest request;
702 request.mutable_log_change()->set_change_type(changeType);
703 request.mutable_log_change()->set_resource_type(Convert(resourceType));
704 request.mutable_log_change()->set_resource_id(internalId);
705 request.mutable_log_change()->set_date(date);
706
707 ExecuteTransaction(DatabasePluginMessages::OPERATION_LOG_CHANGE, request);
708 }
709
710
711 virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
712 {
713 // TODO: "seq" is ignored, could be simplified in "ExportedResource"
714
715 DatabasePluginMessages::TransactionRequest request;
716 request.mutable_log_exported_resource()->set_resource_type(Convert(resource.GetResourceType()));
717 request.mutable_log_exported_resource()->set_public_id(resource.GetPublicId());
718 request.mutable_log_exported_resource()->set_modality(resource.GetModality());
719 request.mutable_log_exported_resource()->set_date(resource.GetDate());
720 request.mutable_log_exported_resource()->set_patient_id(resource.GetPatientId());
721 request.mutable_log_exported_resource()->set_study_instance_uid(resource.GetStudyInstanceUid());
722 request.mutable_log_exported_resource()->set_series_instance_uid(resource.GetSeriesInstanceUid());
723 request.mutable_log_exported_resource()->set_sop_instance_uid(resource.GetSopInstanceUid());
724
725 ExecuteTransaction(DatabasePluginMessages::OPERATION_LOG_EXPORTED_RESOURCE, request);
726 }
727
728
729 virtual bool LookupAttachment(FileInfo& attachment,
730 int64_t& revision,
731 int64_t id,
732 FileContentType contentType) ORTHANC_OVERRIDE
733 {
734 DatabasePluginMessages::TransactionRequest request;
735 request.mutable_lookup_attachment()->set_id(id);
736 request.mutable_lookup_attachment()->set_content_type(contentType);
737
738 DatabasePluginMessages::TransactionResponse response;
739 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_ATTACHMENT, request);
740
741 if (response.lookup_attachment().found())
742 {
743 attachment = Convert(response.lookup_attachment().attachment());
744 revision = response.lookup_attachment().revision();
745 return true;
746 }
747 else
748 {
749 return false;
750 }
751 }
752
753
754 virtual bool LookupGlobalProperty(std::string& target,
755 GlobalProperty property,
756 bool shared) ORTHANC_OVERRIDE
757 {
758 DatabasePluginMessages::TransactionRequest request;
759 request.mutable_lookup_global_property()->set_server_id(shared ? "" : database_.GetServerIdentifier());
760 request.mutable_lookup_global_property()->set_property(property);
761
762 DatabasePluginMessages::TransactionResponse response;
763 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_GLOBAL_PROPERTY, request);
764
765 if (response.lookup_global_property().found())
766 {
767 target = response.lookup_global_property().value();
768 return true;
769 }
770 else
771 {
772 return false;
773 }
774 }
775
776
777 virtual int64_t IncrementGlobalProperty(GlobalProperty property,
778 int64_t increment,
779 bool shared) ORTHANC_OVERRIDE
780 {
781 DatabasePluginMessages::TransactionRequest request;
782 request.mutable_increment_global_property()->set_server_id(shared ? "" : database_.GetServerIdentifier());
783 request.mutable_increment_global_property()->set_property(property);
784 request.mutable_increment_global_property()->set_increment(increment);
785
786 DatabasePluginMessages::TransactionResponse response;
787 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_INCREMENT_GLOBAL_PROPERTY, request);
788
789 return response.increment_global_property().new_value();
790 }
791
792 virtual void UpdateAndGetStatistics(int64_t& patientsCount,
793 int64_t& studiesCount,
794 int64_t& seriesCount,
795 int64_t& instancesCount,
796 int64_t& compressedSize,
797 int64_t& uncompressedSize) ORTHANC_OVERRIDE
798 {
799 DatabasePluginMessages::TransactionResponse response;
800 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_UPDATE_AND_GET_STATISTICS);
801
802 patientsCount = response.update_and_get_statistics().patients_count();
803 studiesCount = response.update_and_get_statistics().studies_count();
804 seriesCount = response.update_and_get_statistics().series_count();
805 instancesCount = response.update_and_get_statistics().instances_count();
806 compressedSize = response.update_and_get_statistics().total_compressed_size();
807 uncompressedSize = response.update_and_get_statistics().total_uncompressed_size();
808 }
809
810 virtual bool LookupMetadata(std::string& target,
811 int64_t& revision,
812 int64_t id,
813 MetadataType type) ORTHANC_OVERRIDE
814 {
815 DatabasePluginMessages::TransactionRequest request;
816 request.mutable_lookup_metadata()->set_id(id);
817 request.mutable_lookup_metadata()->set_metadata_type(type);
818
819 DatabasePluginMessages::TransactionResponse response;
820 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_METADATA, request);
821
822 if (response.lookup_metadata().found())
823 {
824 target = response.lookup_metadata().value();
825 revision = response.lookup_metadata().revision();
826 return true;
827 }
828 else
829 {
830 return false;
831 }
832 }
833
834
835 virtual bool LookupParent(int64_t& parentId,
836 int64_t resourceId) ORTHANC_OVERRIDE
837 {
838 DatabasePluginMessages::TransactionRequest request;
839 request.mutable_lookup_parent()->set_id(resourceId);
840
841 DatabasePluginMessages::TransactionResponse response;
842 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_PARENT, request);
843
844 if (response.lookup_parent().found())
845 {
846 parentId = response.lookup_parent().parent();
847 return true;
848 }
849 else
850 {
851 return false;
852 }
853 }
854
855
856 virtual bool LookupResource(int64_t& id,
857 ResourceType& type,
858 const std::string& publicId) ORTHANC_OVERRIDE
859 {
860 DatabasePluginMessages::TransactionRequest request;
861 request.mutable_lookup_resource()->set_public_id(publicId);
862
863 DatabasePluginMessages::TransactionResponse response;
864 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE, request);
865
866 if (response.lookup_resource().found())
867 {
868 id = response.lookup_resource().internal_id();
869 type = Convert(response.lookup_resource().type());
870 return true;
871 }
872 else
873 {
874 return false;
875 }
876 }
877
878
879 virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
880 {
881 DatabasePluginMessages::TransactionResponse response;
882 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE);
883
884 if (response.select_patient_to_recycle().found())
885 {
886 internalId = response.select_patient_to_recycle().patient_id();
887 return true;
888 }
889 else
890 {
891 return false;
892 }
893 }
894
895
896 virtual bool SelectPatientToRecycle(int64_t& internalId,
897 int64_t patientIdToAvoid) ORTHANC_OVERRIDE
898 {
899 DatabasePluginMessages::TransactionRequest request;
900 request.mutable_select_patient_to_recycle_with_avoid()->set_patient_id_to_avoid(patientIdToAvoid);
901
902 DatabasePluginMessages::TransactionResponse response;
903 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID, request);
904
905 if (response.select_patient_to_recycle_with_avoid().found())
906 {
907 internalId = response.select_patient_to_recycle_with_avoid().patient_id();
908 return true;
909 }
910 else
911 {
912 return false;
913 }
914 }
915
916
917 virtual void SetGlobalProperty(GlobalProperty property,
918 bool shared,
919 const std::string& value) ORTHANC_OVERRIDE
920 {
921 DatabasePluginMessages::TransactionRequest request;
922 request.mutable_set_global_property()->set_server_id(shared ? "" : database_.GetServerIdentifier());
923 request.mutable_set_global_property()->set_property(property);
924 request.mutable_set_global_property()->set_value(value);
925
926 ExecuteTransaction(DatabasePluginMessages::OPERATION_SET_GLOBAL_PROPERTY, request);
927 }
928
929
930 virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
931 {
932 DatabasePluginMessages::TransactionRequest request;
933 request.mutable_clear_main_dicom_tags()->set_id(id);
934
935 ExecuteTransaction(DatabasePluginMessages::OPERATION_CLEAR_MAIN_DICOM_TAGS, request);
936 }
937
938
939 virtual void SetMetadata(int64_t id,
940 MetadataType type,
941 const std::string& value,
942 int64_t revision) ORTHANC_OVERRIDE
943 {
944 DatabasePluginMessages::TransactionRequest request;
945 request.mutable_set_metadata()->set_id(id);
946 request.mutable_set_metadata()->set_metadata_type(type);
947 request.mutable_set_metadata()->set_value(value);
948 request.mutable_set_metadata()->set_revision(revision);
949
950 ExecuteTransaction(DatabasePluginMessages::OPERATION_SET_METADATA, request);
951 }
952
953
954 virtual void SetProtectedPatient(int64_t internalId,
955 bool isProtected) ORTHANC_OVERRIDE
956 {
957 DatabasePluginMessages::TransactionRequest request;
958 request.mutable_set_protected_patient()->set_patient_id(internalId);
959 request.mutable_set_protected_patient()->set_protected_patient(isProtected);
960
961 ExecuteTransaction(DatabasePluginMessages::OPERATION_SET_PROTECTED_PATIENT, request);
962 }
963
964
965 virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
966 {
967 DatabasePluginMessages::TransactionRequest request;
968 request.mutable_is_disk_size_above()->set_threshold(threshold);
969
970 DatabasePluginMessages::TransactionResponse response;
971 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_IS_DISK_SIZE_ABOVE, request);
972
973 return response.is_disk_size_above().result();
974 }
975
976
977 virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
978 std::list<std::string>* instancesId, // Can be NULL if not needed
979 const DatabaseConstraints& lookup,
980 ResourceType queryLevel,
981 const std::set<std::string>& labels,
982 LabelsConstraint labelsConstraint,
983 uint32_t limit) ORTHANC_OVERRIDE
984 {
985 if (!database_.GetDatabaseCapabilities().HasLabelsSupport() &&
986 !labels.empty())
987 {
988 throw OrthancException(ErrorCode_InternalError);
989 }
990
991 DatabasePluginMessages::TransactionRequest request;
992 request.mutable_lookup_resources()->set_query_level(Convert(queryLevel));
993 request.mutable_lookup_resources()->set_limit(limit);
994 request.mutable_lookup_resources()->set_retrieve_instances_ids(instancesId != NULL);
995
996 request.mutable_lookup_resources()->mutable_lookup()->Reserve(lookup.GetSize());
997
998 for (size_t i = 0; i < lookup.GetSize(); i++)
999 {
1000 const DatabaseConstraint& source = lookup.GetConstraint(i);
1001
1002 DatabasePluginMessages::DatabaseConstraint* target = request.mutable_lookup_resources()->add_lookup();
1003 target->set_level(Convert(source.GetLevel()));
1004 target->set_tag_group(source.GetTag().GetGroup());
1005 target->set_tag_element(source.GetTag().GetElement());
1006 target->set_is_identifier_tag(source.IsIdentifier());
1007 target->set_is_case_sensitive(source.IsCaseSensitive());
1008 target->set_is_mandatory(source.IsMandatory());
1009
1010 target->mutable_values()->Reserve(source.GetValuesCount());
1011 for (size_t j = 0; j < source.GetValuesCount(); j++)
1012 {
1013 target->add_values(source.GetValue(j));
1014 }
1015
1016 switch (source.GetConstraintType())
1017 {
1018 case ConstraintType_Equal:
1019 target->set_type(DatabasePluginMessages::CONSTRAINT_EQUAL);
1020 break;
1021
1022 case ConstraintType_SmallerOrEqual:
1023 target->set_type(DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL);
1024 break;
1025
1026 case ConstraintType_GreaterOrEqual:
1027 target->set_type(DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL);
1028 break;
1029
1030 case ConstraintType_Wildcard:
1031 target->set_type(DatabasePluginMessages::CONSTRAINT_WILDCARD);
1032 break;
1033
1034 case ConstraintType_List:
1035 target->set_type(DatabasePluginMessages::CONSTRAINT_LIST);
1036 break;
1037
1038 default:
1039 throw OrthancException(ErrorCode_ParameterOutOfRange);
1040 }
1041 }
1042
1043 for (std::set<std::string>::const_iterator it = labels.begin(); it != labels.end(); ++it)
1044 {
1045 request.mutable_lookup_resources()->add_labels(*it);
1046 }
1047
1048 switch (labelsConstraint)
1049 {
1050 case LabelsConstraint_All:
1051 request.mutable_lookup_resources()->set_labels_constraint(DatabasePluginMessages::LABELS_CONSTRAINT_ALL);
1052 break;
1053
1054 case LabelsConstraint_Any:
1055 request.mutable_lookup_resources()->set_labels_constraint(DatabasePluginMessages::LABELS_CONSTRAINT_ANY);
1056 break;
1057
1058 case LabelsConstraint_None:
1059 request.mutable_lookup_resources()->set_labels_constraint(DatabasePluginMessages::LABELS_CONSTRAINT_NONE);
1060 break;
1061
1062 default:
1063 throw OrthancException(ErrorCode_ParameterOutOfRange);
1064 }
1065
1066 DatabasePluginMessages::TransactionResponse response;
1067 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_RESOURCES, request);
1068
1069 for (int i = 0; i < response.lookup_resources().resources_ids().size(); i++)
1070 {
1071 resourcesId.push_back(response.lookup_resources().resources_ids(i));
1072 }
1073
1074 if (instancesId != NULL)
1075 {
1076 if (response.lookup_resources().resources_ids().size() != response.lookup_resources().instances_ids().size())
98 { 1077 {
99 throw OrthancException(ErrorCode_DatabasePlugin); 1078 throw OrthancException(ErrorCode_DatabasePlugin);
100 } 1079 }
101 else 1080 else
102 { 1081 {
103 target.push_back(value); 1082 for (int i = 0; i < response.lookup_resources().instances_ids().size(); i++)
104 }
105 }
106 }
107
108
109 bool ReadSingleStringAnswer(std::string& target)
110 {
111 uint32_t count;
112 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
113
114 if (count == 0)
115 {
116 return false;
117 }
118 else if (count == 1)
119 {
120 const char* value = NULL;
121 CheckSuccess(that_.backend_.readAnswerString(transaction_, &value, 0));
122 if (value == NULL)
123 {
124 throw OrthancException(ErrorCode_DatabasePlugin);
125 }
126 else
127 {
128 target.assign(value);
129 return true;
130 }
131 }
132 else
133 {
134 throw OrthancException(ErrorCode_DatabasePlugin);
135 }
136 }
137
138
139 bool ReadSingleInt64Answer(int64_t& target)
140 {
141 uint32_t count;
142 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
143
144 if (count == 0)
145 {
146 return false;
147 }
148 else if (count == 1)
149 {
150 CheckSuccess(that_.backend_.readAnswerInt64(transaction_, &target, 0));
151 return true;
152 }
153 else
154 {
155 throw OrthancException(ErrorCode_DatabasePlugin);
156 }
157 }
158
159
160 ExportedResource ReadAnswerExportedResource(uint32_t answerIndex)
161 {
162 OrthancPluginExportedResource exported;
163 CheckSuccess(that_.backend_.readAnswerExportedResource(transaction_, &exported, answerIndex));
164
165 if (exported.publicId == NULL ||
166 exported.modality == NULL ||
167 exported.date == NULL ||
168 exported.patientId == NULL ||
169 exported.studyInstanceUid == NULL ||
170 exported.seriesInstanceUid == NULL ||
171 exported.sopInstanceUid == NULL)
172 {
173 throw OrthancException(ErrorCode_DatabasePlugin);
174 }
175 else
176 {
177 return ExportedResource(exported.seq,
178 Plugins::Convert(exported.resourceType),
179 exported.publicId,
180 exported.modality,
181 exported.date,
182 exported.patientId,
183 exported.studyInstanceUid,
184 exported.seriesInstanceUid,
185 exported.sopInstanceUid);
186 }
187 }
188
189
190 ServerIndexChange ReadAnswerChange(uint32_t answerIndex)
191 {
192 OrthancPluginChange change;
193 CheckSuccess(that_.backend_.readAnswerChange(transaction_, &change, answerIndex));
194
195 if (change.publicId == NULL ||
196 change.date == NULL)
197 {
198 throw OrthancException(ErrorCode_DatabasePlugin);
199 }
200 else
201 {
202 return ServerIndexChange(change.seq,
203 static_cast<ChangeType>(change.changeType),
204 Plugins::Convert(change.resourceType),
205 change.publicId,
206 change.date);
207 }
208 }
209
210
211 void CheckNoEvent()
212 {
213 uint32_t count;
214 CheckSuccess(that_.backend_.readEventsCount(transaction_, &count));
215 if (count != 0)
216 {
217 throw OrthancException(ErrorCode_DatabasePlugin);
218 }
219 }
220
221
222 void ProcessEvents(bool isDeletingAttachment)
223 {
224 uint32_t count;
225 CheckSuccess(that_.backend_.readEventsCount(transaction_, &count));
226
227 for (uint32_t i = 0; i < count; i++)
228 {
229 OrthancPluginDatabaseEvent2 event;
230 CheckSuccess(that_.backend_.readEvent2(transaction_, &event, i));
231
232 switch (event.type)
233 {
234 case OrthancPluginDatabaseEventType_DeletedAttachment:
235 listener_.SignalAttachmentDeleted(Convert(event.content.attachment));
236 break;
237
238 case OrthancPluginDatabaseEventType_DeletedResource:
239 if (isDeletingAttachment)
240 {
241 // This event should only be triggered by "DeleteResource()"
242 throw OrthancException(ErrorCode_DatabasePlugin);
243 }
244 else
245 {
246 listener_.SignalResourceDeleted(Plugins::Convert(event.content.resource.level), event.content.resource.publicId);
247 }
248 break;
249
250 case OrthancPluginDatabaseEventType_RemainingAncestor:
251 if (isDeletingAttachment)
252 {
253 // This event should only triggered by "DeleteResource()"
254 throw OrthancException(ErrorCode_DatabasePlugin);
255 }
256 else
257 {
258 listener_.SignalRemainingAncestor(Plugins::Convert(event.content.resource.level), event.content.resource.publicId);
259 }
260 break;
261
262 default:
263 break; // Unhandled event
264 }
265 }
266 }
267
268
269 public:
270 Transaction(OrthancPluginDatabaseV4& that,
271 IDatabaseListener& listener,
272 OrthancPluginDatabaseTransactionType type) :
273 that_(that),
274 listener_(listener)
275 {
276 CheckSuccess(that.backend_.startTransaction(that.database_, &transaction_, type));
277 if (transaction_ == NULL)
278 {
279 throw OrthancException(ErrorCode_DatabasePlugin);
280 }
281 }
282
283
284 virtual ~Transaction()
285 {
286 OrthancPluginErrorCode code = that_.backend_.destructTransaction(transaction_);
287 if (code != OrthancPluginErrorCode_Success)
288 {
289 // Don't throw exception in destructors
290 that_.errorDictionary_.LogError(code, true);
291 }
292 }
293
294
295 virtual void Rollback() ORTHANC_OVERRIDE
296 {
297 CheckSuccess(that_.backend_.rollback(transaction_));
298 CheckNoEvent();
299 }
300
301
302 virtual void Commit(int64_t fileSizeDelta) ORTHANC_OVERRIDE
303 {
304 CheckSuccess(that_.backend_.commit(transaction_, fileSizeDelta));
305 CheckNoEvent();
306 }
307
308
309 virtual void AddAttachment(int64_t id,
310 const FileInfo& attachment,
311 int64_t revision) ORTHANC_OVERRIDE
312 {
313 OrthancPluginAttachment2 tmp;
314 tmp.uuid = attachment.GetUuid().c_str();
315 tmp.contentType = static_cast<int32_t>(attachment.GetContentType());
316 tmp.uncompressedSize = attachment.GetUncompressedSize();
317 tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str();
318 tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType());
319 tmp.compressedSize = attachment.GetCompressedSize();
320 tmp.compressedHash = attachment.GetCompressedMD5().c_str();
321 tmp.customData = attachment.GetCustomData().c_str();
322
323 CheckSuccess(that_.backend_.addAttachment2(transaction_, id, &tmp, revision));
324 CheckNoEvent();
325 }
326
327
328 virtual void ClearChanges() ORTHANC_OVERRIDE
329 {
330 CheckSuccess(that_.backend_.clearChanges(transaction_));
331 CheckNoEvent();
332 }
333
334
335 virtual void ClearExportedResources() ORTHANC_OVERRIDE
336 {
337 CheckSuccess(that_.backend_.clearExportedResources(transaction_));
338 CheckNoEvent();
339 }
340
341
342 virtual void DeleteAttachment(int64_t id,
343 FileContentType attachment) ORTHANC_OVERRIDE
344 {
345 CheckSuccess(that_.backend_.deleteAttachment(transaction_, id, static_cast<int32_t>(attachment)));
346 ProcessEvents(true);
347 }
348
349
350 virtual void DeleteMetadata(int64_t id,
351 MetadataType type) ORTHANC_OVERRIDE
352 {
353 CheckSuccess(that_.backend_.deleteMetadata(transaction_, id, static_cast<int32_t>(type)));
354 CheckNoEvent();
355 }
356
357
358 virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
359 {
360 CheckSuccess(that_.backend_.deleteResource(transaction_, id));
361 ProcessEvents(false);
362 }
363
364
365 virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
366 int64_t id) ORTHANC_OVERRIDE
367 {
368 CheckSuccess(that_.backend_.getAllMetadata(transaction_, id));
369 CheckNoEvent();
370
371 uint32_t count;
372 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
373
374 target.clear();
375 for (uint32_t i = 0; i < count; i++)
376 {
377 int32_t metadata;
378 const char* value = NULL;
379 CheckSuccess(that_.backend_.readAnswerMetadata(transaction_, &metadata, &value, i));
380
381 if (value == NULL)
382 {
383 throw OrthancException(ErrorCode_DatabasePlugin);
384 }
385 else
386 {
387 target[static_cast<MetadataType>(metadata)] = value;
388 }
389 }
390 }
391
392
393 virtual void GetAllPublicIds(std::list<std::string>& target,
394 ResourceType resourceType) ORTHANC_OVERRIDE
395 {
396 CheckSuccess(that_.backend_.getAllPublicIds(transaction_, Plugins::Convert(resourceType)));
397 CheckNoEvent();
398
399 ReadStringAnswers(target);
400 }
401
402
403 virtual void GetAllPublicIds(std::list<std::string>& target,
404 ResourceType resourceType,
405 size_t since,
406 size_t limit) ORTHANC_OVERRIDE
407 {
408 CheckSuccess(that_.backend_.getAllPublicIdsWithLimit(
409 transaction_, Plugins::Convert(resourceType),
410 static_cast<uint64_t>(since), static_cast<uint64_t>(limit)));
411 CheckNoEvent();
412
413 ReadStringAnswers(target);
414 }
415
416
417 virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
418 bool& done /*out*/,
419 int64_t since,
420 uint32_t maxResults) ORTHANC_OVERRIDE
421 {
422 uint8_t tmpDone = true;
423 CheckSuccess(that_.backend_.getChanges(transaction_, &tmpDone, since, maxResults));
424 CheckNoEvent();
425
426 done = (tmpDone != 0);
427
428 uint32_t count;
429 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
430
431 target.clear();
432 for (uint32_t i = 0; i < count; i++)
433 {
434 target.push_back(ReadAnswerChange(i));
435 }
436 }
437
438
439 virtual void GetChildrenInternalId(std::list<int64_t>& target,
440 int64_t id) ORTHANC_OVERRIDE
441 {
442 CheckSuccess(that_.backend_.getChildrenInternalId(transaction_, id));
443 CheckNoEvent();
444
445 uint32_t count;
446 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
447
448 target.clear();
449 for (uint32_t i = 0; i < count; i++)
450 {
451 int64_t value;
452 CheckSuccess(that_.backend_.readAnswerInt64(transaction_, &value, i));
453 target.push_back(value);
454 }
455 }
456
457
458 virtual void GetChildrenPublicId(std::list<std::string>& target,
459 int64_t id) ORTHANC_OVERRIDE
460 {
461 CheckSuccess(that_.backend_.getChildrenPublicId(transaction_, id));
462 CheckNoEvent();
463
464 ReadStringAnswers(target);
465 }
466
467
468 virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
469 bool& done /*out*/,
470 int64_t since,
471 uint32_t maxResults) ORTHANC_OVERRIDE
472 {
473 uint8_t tmpDone = true;
474 CheckSuccess(that_.backend_.getExportedResources(transaction_, &tmpDone, since, maxResults));
475 CheckNoEvent();
476
477 done = (tmpDone != 0);
478
479 uint32_t count;
480 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
481
482 target.clear();
483 for (uint32_t i = 0; i < count; i++)
484 {
485 target.push_back(ReadAnswerExportedResource(i));
486 }
487 }
488
489
490 virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
491 {
492 CheckSuccess(that_.backend_.getLastChange(transaction_));
493 CheckNoEvent();
494
495 uint32_t count;
496 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
497
498 target.clear();
499 if (count == 1)
500 {
501 target.push_back(ReadAnswerChange(0));
502 }
503 else if (count > 1)
504 {
505 throw OrthancException(ErrorCode_DatabasePlugin);
506 }
507 }
508
509
510 virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
511 {
512 CheckSuccess(that_.backend_.getLastExportedResource(transaction_));
513 CheckNoEvent();
514
515 uint32_t count;
516 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
517
518 target.clear();
519 if (count == 1)
520 {
521 target.push_back(ReadAnswerExportedResource(0));
522 }
523 else if (count > 1)
524 {
525 throw OrthancException(ErrorCode_DatabasePlugin);
526 }
527 }
528
529
530 virtual void GetMainDicomTags(DicomMap& target,
531 int64_t id) ORTHANC_OVERRIDE
532 {
533 CheckSuccess(that_.backend_.getMainDicomTags(transaction_, id));
534 CheckNoEvent();
535
536 uint32_t count;
537 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
538
539 target.Clear();
540 for (uint32_t i = 0; i < count; i++)
541 {
542 uint16_t group, element;
543 const char* value = NULL;
544 CheckSuccess(that_.backend_.readAnswerDicomTag(transaction_, &group, &element, &value, i));
545
546 if (value == NULL)
547 {
548 throw OrthancException(ErrorCode_DatabasePlugin);
549 }
550 else
551 {
552 target.SetValue(group, element, std::string(value), false);
553 }
554 }
555 }
556
557
558 virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
559 {
560 CheckSuccess(that_.backend_.getPublicId(transaction_, resourceId));
561 CheckNoEvent();
562
563 std::string s;
564 if (ReadSingleStringAnswer(s))
565 {
566 return s;
567 }
568 else
569 {
570 throw OrthancException(ErrorCode_InexistentItem);
571 }
572 }
573
574
575 virtual uint64_t GetResourcesCount(ResourceType resourceType) ORTHANC_OVERRIDE
576 {
577 uint64_t value;
578 CheckSuccess(that_.backend_.getResourcesCount(transaction_, &value, Plugins::Convert(resourceType)));
579 CheckNoEvent();
580 return value;
581 }
582
583
584 virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
585 {
586 OrthancPluginResourceType type;
587 CheckSuccess(that_.backend_.getResourceType(transaction_, &type, resourceId));
588 CheckNoEvent();
589 return Plugins::Convert(type);
590 }
591
592
593 virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
594 {
595 uint64_t s;
596 CheckSuccess(that_.backend_.getTotalCompressedSize(transaction_, &s));
597 CheckNoEvent();
598 return s;
599 }
600
601
602 virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
603 {
604 uint64_t s;
605 CheckSuccess(that_.backend_.getTotalUncompressedSize(transaction_, &s));
606 CheckNoEvent();
607 return s;
608 }
609
610
611 virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE
612 {
613 uint8_t b;
614 CheckSuccess(that_.backend_.isExistingResource(transaction_, &b, internalId));
615 CheckNoEvent();
616 return (b != 0);
617 }
618
619
620 virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
621 {
622 uint8_t b;
623 CheckSuccess(that_.backend_.isProtectedPatient(transaction_, &b, internalId));
624 CheckNoEvent();
625 return (b != 0);
626 }
627
628
629 virtual void ListAvailableAttachments(std::set<FileContentType>& target,
630 int64_t id) ORTHANC_OVERRIDE
631 {
632 CheckSuccess(that_.backend_.listAvailableAttachments(transaction_, id));
633 CheckNoEvent();
634
635 uint32_t count;
636 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
637
638 target.clear();
639 for (uint32_t i = 0; i < count; i++)
640 {
641 int32_t value;
642 CheckSuccess(that_.backend_.readAnswerInt32(transaction_, &value, i));
643 target.insert(static_cast<FileContentType>(value));
644 }
645 }
646
647
648 virtual void LogChange(int64_t internalId,
649 const ServerIndexChange& change) ORTHANC_OVERRIDE
650 {
651 CheckSuccess(that_.backend_.logChange(transaction_, static_cast<int32_t>(change.GetChangeType()),
652 internalId, Plugins::Convert(change.GetResourceType()),
653 change.GetDate().c_str()));
654 CheckNoEvent();
655 }
656
657
658 virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
659 {
660 CheckSuccess(that_.backend_.logExportedResource(transaction_, Plugins::Convert(resource.GetResourceType()),
661 resource.GetPublicId().c_str(),
662 resource.GetModality().c_str(),
663 resource.GetDate().c_str(),
664 resource.GetPatientId().c_str(),
665 resource.GetStudyInstanceUid().c_str(),
666 resource.GetSeriesInstanceUid().c_str(),
667 resource.GetSopInstanceUid().c_str()));
668 CheckNoEvent();
669 }
670
671
672 virtual bool LookupAttachment(FileInfo& attachment,
673 int64_t& revision,
674 int64_t id,
675 FileContentType contentType) ORTHANC_OVERRIDE
676 {
677 CheckSuccess(that_.backend_.lookupAttachment(transaction_, &revision, id, static_cast<int32_t>(contentType)));
678 CheckNoEvent();
679
680 uint32_t count;
681 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
682
683 if (count == 0)
684 {
685 return false;
686 }
687 else if (count == 1)
688 {
689 OrthancPluginAttachment2 tmp;
690 CheckSuccess(that_.backend_.readAnswerAttachment2(transaction_, &tmp, 0));
691 attachment = Convert(tmp);
692 return true;
693 }
694 else
695 {
696 throw OrthancException(ErrorCode_DatabasePlugin);
697 }
698 }
699
700
701 virtual bool LookupGlobalProperty(std::string& target,
702 GlobalProperty property,
703 bool shared) ORTHANC_OVERRIDE
704 {
705 const char* id = (shared ? "" : that_.serverIdentifier_.c_str());
706
707 CheckSuccess(that_.backend_.lookupGlobalProperty(transaction_, id, static_cast<int32_t>(property)));
708 CheckNoEvent();
709 return ReadSingleStringAnswer(target);
710 }
711
712
713 virtual bool LookupMetadata(std::string& target,
714 int64_t& revision,
715 int64_t id,
716 MetadataType type) ORTHANC_OVERRIDE
717 {
718 CheckSuccess(that_.backend_.lookupMetadata(transaction_, &revision, id, static_cast<int32_t>(type)));
719 CheckNoEvent();
720 return ReadSingleStringAnswer(target);
721 }
722
723
724 virtual bool LookupParent(int64_t& parentId,
725 int64_t resourceId) ORTHANC_OVERRIDE
726 {
727 uint8_t existing;
728 CheckSuccess(that_.backend_.lookupParent(transaction_, &existing, &parentId, resourceId));
729 CheckNoEvent();
730 return (existing != 0);
731 }
732
733
734 virtual bool LookupResource(int64_t& id,
735 ResourceType& type,
736 const std::string& publicId) ORTHANC_OVERRIDE
737 {
738 uint8_t existing;
739 OrthancPluginResourceType t;
740 CheckSuccess(that_.backend_.lookupResource(transaction_, &existing, &id, &t, publicId.c_str()));
741 CheckNoEvent();
742
743 if (existing == 0)
744 {
745 return false;
746 }
747 else
748 {
749 type = Plugins::Convert(t);
750 return true;
751 }
752 }
753
754
755 virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
756 {
757 uint8_t available;
758 CheckSuccess(that_.backend_.selectPatientToRecycle(transaction_, &available, &internalId));
759 CheckNoEvent();
760 return (available != 0);
761 }
762
763
764 virtual bool SelectPatientToRecycle(int64_t& internalId,
765 int64_t patientIdToAvoid) ORTHANC_OVERRIDE
766 {
767 uint8_t available;
768 CheckSuccess(that_.backend_.selectPatientToRecycle2(transaction_, &available, &internalId, patientIdToAvoid));
769 CheckNoEvent();
770 return (available != 0);
771 }
772
773
774 virtual void SetGlobalProperty(GlobalProperty property,
775 bool shared,
776 const std::string& value) ORTHANC_OVERRIDE
777 {
778 const char* id = (shared ? "" : that_.serverIdentifier_.c_str());
779
780 CheckSuccess(that_.backend_.setGlobalProperty(transaction_, id, static_cast<int32_t>(property), value.c_str()));
781 CheckNoEvent();
782 }
783
784
785 virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
786 {
787 CheckSuccess(that_.backend_.clearMainDicomTags(transaction_, id));
788 CheckNoEvent();
789 }
790
791
792 virtual void SetMetadata(int64_t id,
793 MetadataType type,
794 const std::string& value,
795 int64_t revision) ORTHANC_OVERRIDE
796 {
797 CheckSuccess(that_.backend_.setMetadata(transaction_, id, static_cast<int32_t>(type), value.c_str(), revision));
798 CheckNoEvent();
799 }
800
801
802 virtual void SetProtectedPatient(int64_t internalId,
803 bool isProtected) ORTHANC_OVERRIDE
804 {
805 CheckSuccess(that_.backend_.setProtectedPatient(transaction_, internalId, (isProtected ? 1 : 0)));
806 CheckNoEvent();
807 }
808
809
810 virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
811 {
812 uint8_t tmp;
813 CheckSuccess(that_.backend_.isDiskSizeAbove(transaction_, &tmp, threshold));
814 CheckNoEvent();
815 return (tmp != 0);
816 }
817
818
819 virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
820 std::list<std::string>* instancesId, // Can be NULL if not needed
821 const std::vector<DatabaseConstraint>& lookup,
822 ResourceType queryLevel,
823 size_t limit) ORTHANC_OVERRIDE
824 {
825 std::vector<OrthancPluginDatabaseConstraint> constraints;
826 std::vector< std::vector<const char*> > constraintsValues;
827
828 constraints.resize(lookup.size());
829 constraintsValues.resize(lookup.size());
830
831 for (size_t i = 0; i < lookup.size(); i++)
832 {
833 lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]);
834 }
835
836 CheckSuccess(that_.backend_.lookupResources(transaction_, lookup.size(),
837 (lookup.empty() ? NULL : &constraints[0]),
838 Plugins::Convert(queryLevel),
839 limit, (instancesId == NULL ? 0 : 1)));
840 CheckNoEvent();
841
842 uint32_t count;
843 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
844
845 resourcesId.clear();
846
847 if (instancesId != NULL)
848 {
849 instancesId->clear();
850 }
851
852 for (uint32_t i = 0; i < count; i++)
853 {
854 OrthancPluginMatchingResource resource;
855 CheckSuccess(that_.backend_.readAnswerMatchingResource(transaction_, &resource, i));
856
857 if (resource.resourceId == NULL)
858 {
859 throw OrthancException(ErrorCode_DatabasePlugin);
860 }
861
862 resourcesId.push_back(resource.resourceId);
863
864 if (instancesId != NULL)
865 {
866 if (resource.someInstanceId == NULL)
867 { 1083 {
868 throw OrthancException(ErrorCode_DatabasePlugin); 1084 instancesId->push_back(response.lookup_resources().instances_ids(i));
869 }
870 else
871 {
872 instancesId->push_back(resource.someInstanceId);
873 } 1085 }
874 } 1086 }
875 } 1087 }
876 } 1088 }
877 1089
881 const std::string& patient, 1093 const std::string& patient,
882 const std::string& study, 1094 const std::string& study,
883 const std::string& series, 1095 const std::string& series,
884 const std::string& instance) ORTHANC_OVERRIDE 1096 const std::string& instance) ORTHANC_OVERRIDE
885 { 1097 {
886 OrthancPluginCreateInstanceResult output; 1098 // TODO: "CreateInstanceResult" => constructor and getters
887 memset(&output, 0, sizeof(output));
888
889 CheckSuccess(that_.backend_.createInstance(transaction_, &output, patient.c_str(),
890 study.c_str(), series.c_str(), instance.c_str()));
891 CheckNoEvent();
892
893 instanceId = output.instanceId;
894 1099
895 if (output.isNewInstance) 1100 DatabasePluginMessages::TransactionRequest request;
896 { 1101 request.mutable_create_instance()->set_patient(patient);
897 result.isNewPatient_ = output.isNewPatient; 1102 request.mutable_create_instance()->set_study(study);
898 result.isNewStudy_ = output.isNewStudy; 1103 request.mutable_create_instance()->set_series(series);
899 result.isNewSeries_ = output.isNewSeries; 1104 request.mutable_create_instance()->set_instance(instance);
900 result.patientId_ = output.patientId; 1105
901 result.studyId_ = output.studyId; 1106 DatabasePluginMessages::TransactionResponse response;
902 result.seriesId_ = output.seriesId; 1107 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_CREATE_INSTANCE, request);
1108
1109 instanceId = response.create_instance().instance_id();
1110
1111 if (response.create_instance().is_new_instance())
1112 {
1113 result.isNewPatient_ = response.create_instance().is_new_patient();
1114 result.isNewStudy_ = response.create_instance().is_new_study();
1115 result.isNewSeries_ = response.create_instance().is_new_series();
1116 result.patientId_ = response.create_instance().patient_id();
1117 result.studyId_ = response.create_instance().study_id();
1118 result.seriesId_ = response.create_instance().series_id();
903 return true; 1119 return true;
904 } 1120 }
905 else 1121 else
906 { 1122 {
907 return false; 1123 return false;
908 } 1124 }
909
910 } 1125 }
911 1126
912 1127
913 virtual void SetResourcesContent(const ResourcesContent& content) ORTHANC_OVERRIDE 1128 virtual void SetResourcesContent(const ResourcesContent& content) ORTHANC_OVERRIDE
914 { 1129 {
915 std::vector<OrthancPluginResourcesContentTags> identifierTags; 1130 DatabasePluginMessages::TransactionRequest request;
916 std::vector<OrthancPluginResourcesContentTags> mainDicomTags; 1131
917 std::vector<OrthancPluginResourcesContentMetadata> metadata; 1132 request.mutable_set_resources_content()->mutable_tags()->Reserve(content.GetListTags().size());
918 1133 for (ResourcesContent::ListTags::const_iterator it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
919 identifierTags.reserve(content.GetListTags().size()); 1134 {
920 mainDicomTags.reserve(content.GetListTags().size()); 1135 DatabasePluginMessages::SetResourcesContent_Request_Tag* tag = request.mutable_set_resources_content()->add_tags();
921 metadata.reserve(content.GetListMetadata().size()); 1136 tag->set_resource_id(it->GetResourceId());
922 1137 tag->set_is_identifier(it->IsIdentifier());
923 for (ResourcesContent::ListTags::const_iterator 1138 tag->set_group(it->GetTag().GetGroup());
924 it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it) 1139 tag->set_element(it->GetTag().GetElement());
925 { 1140 tag->set_value(it->GetValue());
926 OrthancPluginResourcesContentTags tmp; 1141 }
927 tmp.resource = it->resourceId_; 1142
928 tmp.group = it->tag_.GetGroup(); 1143 request.mutable_set_resources_content()->mutable_metadata()->Reserve(content.GetListMetadata().size());
929 tmp.element = it->tag_.GetElement(); 1144 for (ResourcesContent::ListMetadata::const_iterator it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
930 tmp.value = it->value_.c_str(); 1145 {
931 1146 DatabasePluginMessages::SetResourcesContent_Request_Metadata* metadata = request.mutable_set_resources_content()->add_metadata();
932 if (it->isIdentifier_) 1147 metadata->set_resource_id(it->GetResourceId());
933 { 1148 metadata->set_metadata(it->GetType());
934 identifierTags.push_back(tmp); 1149 metadata->set_value(it->GetValue());
935 } 1150 }
936 else 1151
937 { 1152 ExecuteTransaction(DatabasePluginMessages::OPERATION_SET_RESOURCES_CONTENT, request);
938 mainDicomTags.push_back(tmp);
939 }
940 }
941
942 for (ResourcesContent::ListMetadata::const_iterator
943 it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
944 {
945 OrthancPluginResourcesContentMetadata tmp;
946 tmp.resource = it->resourceId_;
947 tmp.metadata = it->metadata_;
948 tmp.value = it->value_.c_str();
949 metadata.push_back(tmp);
950 }
951
952 assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
953 metadata.size() == content.GetListMetadata().size());
954
955 CheckSuccess(that_.backend_.setResourcesContent(transaction_,
956 identifierTags.size(),
957 (identifierTags.empty() ? NULL : &identifierTags[0]),
958 mainDicomTags.size(),
959 (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
960 metadata.size(),
961 (metadata.empty() ? NULL : &metadata[0])));
962 CheckNoEvent();
963 } 1153 }
964 1154
965 1155
966 virtual void GetChildrenMetadata(std::list<std::string>& target, 1156 virtual void GetChildrenMetadata(std::list<std::string>& target,
967 int64_t resourceId, 1157 int64_t resourceId,
968 MetadataType metadata) ORTHANC_OVERRIDE 1158 MetadataType metadata) ORTHANC_OVERRIDE
969 { 1159 {
970 CheckSuccess(that_.backend_.getChildrenMetadata(transaction_, resourceId, static_cast<int32_t>(metadata))); 1160 DatabasePluginMessages::TransactionRequest request;
971 CheckNoEvent(); 1161 request.mutable_get_children_metadata()->set_id(resourceId);
972 ReadStringAnswers(target); 1162 request.mutable_get_children_metadata()->set_metadata(metadata);
1163
1164 DatabasePluginMessages::TransactionResponse response;
1165 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_CHILDREN_METADATA, request);
1166
1167 for (int i = 0; i < response.get_children_metadata().values().size(); i++)
1168 {
1169 target.push_back(response.get_children_metadata().values(i));
1170 }
973 } 1171 }
974 1172
975 1173
976 virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE 1174 virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
977 { 1175 {
978 int64_t tmp; 1176 DatabasePluginMessages::TransactionResponse response;
979 CheckSuccess(that_.backend_.getLastChangeIndex(transaction_, &tmp)); 1177 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_GET_LAST_CHANGE_INDEX);
980 CheckNoEvent(); 1178 return response.get_last_change_index().result();
981 return tmp;
982 } 1179 }
983 1180
984 1181
985 virtual bool LookupResourceAndParent(int64_t& id, 1182 virtual bool LookupResourceAndParent(int64_t& id,
986 ResourceType& type, 1183 ResourceType& type,
987 std::string& parentPublicId, 1184 std::string& parentPublicId,
988 const std::string& publicId) ORTHANC_OVERRIDE 1185 const std::string& publicId) ORTHANC_OVERRIDE
989 { 1186 {
990 uint8_t isExisting; 1187 DatabasePluginMessages::TransactionRequest request;
991 OrthancPluginResourceType tmpType; 1188 request.mutable_lookup_resource_and_parent()->set_public_id(publicId);
992 CheckSuccess(that_.backend_.lookupResourceAndParent(transaction_, &isExisting, &id, &tmpType, publicId.c_str())); 1189
993 CheckNoEvent(); 1190 DatabasePluginMessages::TransactionResponse response;
994 1191 ExecuteTransaction(response, DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE_AND_PARENT, request);
995 if (isExisting) 1192
996 { 1193 if (response.lookup_resource_and_parent().found())
997 type = Plugins::Convert(tmpType); 1194 {
998 1195 id = response.lookup_resource_and_parent().id();
999 uint32_t count; 1196 type = Convert(response.lookup_resource_and_parent().type());
1000 CheckSuccess(that_.backend_.readAnswersCount(transaction_, &count));
1001
1002 if (count > 1)
1003 {
1004 throw OrthancException(ErrorCode_DatabasePlugin);
1005 }
1006 1197
1007 switch (type) 1198 switch (type)
1008 { 1199 {
1009 case ResourceType_Patient: 1200 case ResourceType_Patient:
1010 // A patient has no parent 1201 if (!response.lookup_resource_and_parent().parent_public_id().empty())
1011 if (count == 1)
1012 { 1202 {
1013 throw OrthancException(ErrorCode_DatabasePlugin); 1203 throw OrthancException(ErrorCode_DatabasePlugin);
1014 } 1204 }
1015 break; 1205 break;
1016 1206
1017 case ResourceType_Study: 1207 case ResourceType_Study:
1018 case ResourceType_Series: 1208 case ResourceType_Series:
1019 case ResourceType_Instance: 1209 case ResourceType_Instance:
1020 if (count == 0) 1210 if (response.lookup_resource_and_parent().parent_public_id().empty())
1021 { 1211 {
1022 throw OrthancException(ErrorCode_DatabasePlugin); 1212 throw OrthancException(ErrorCode_DatabasePlugin);
1023 } 1213 }
1024 else 1214 else
1025 { 1215 {
1026 const char* value = NULL; 1216 parentPublicId = response.lookup_resource_and_parent().parent_public_id();
1027 CheckSuccess(that_.backend_.readAnswerString(transaction_, &value, 0));
1028 if (value == NULL)
1029 {
1030 throw OrthancException(ErrorCode_DatabasePlugin);
1031 }
1032 else
1033 {
1034 parentPublicId.assign(value);
1035 }
1036 } 1217 }
1037 break; 1218 break;
1038 1219
1039 default: 1220 default:
1040 throw OrthancException(ErrorCode_DatabasePlugin); 1221 throw OrthancException(ErrorCode_ParameterOutOfRange);
1041 } 1222 }
1042 1223
1043 return true; 1224 return true;
1044 } 1225 }
1045 else 1226 else
1046 { 1227 {
1047 return false; 1228 return false;
1048 } 1229 }
1049 } 1230 }
1231
1232
1233 virtual void AddLabel(int64_t resource,
1234 const std::string& label) ORTHANC_OVERRIDE
1235 {
1236 if (database_.GetDatabaseCapabilities().HasLabelsSupport())
1237 {
1238 DatabasePluginMessages::TransactionRequest request;
1239 request.mutable_add_label()->set_id(resource);
1240 request.mutable_add_label()->set_label(label);
1241
1242 ExecuteTransaction(DatabasePluginMessages::OPERATION_ADD_LABEL, request);
1243 }
1244 else
1245 {
1246 // This method shouldn't have been called
1247 throw OrthancException(ErrorCode_InternalError);
1248 }
1249 }
1250
1251
1252 virtual void RemoveLabel(int64_t resource,
1253 const std::string& label) ORTHANC_OVERRIDE
1254 {
1255 if (database_.GetDatabaseCapabilities().HasLabelsSupport())
1256 {
1257 DatabasePluginMessages::TransactionRequest request;
1258 request.mutable_remove_label()->set_id(resource);
1259 request.mutable_remove_label()->set_label(label);
1260
1261 ExecuteTransaction(DatabasePluginMessages::OPERATION_REMOVE_LABEL, request);
1262 }
1263 else
1264 {
1265 // This method shouldn't have been called
1266 throw OrthancException(ErrorCode_InternalError);
1267 }
1268 }
1269
1270
1271 virtual void ListLabels(std::set<std::string>& target,
1272 int64_t resource) ORTHANC_OVERRIDE
1273 {
1274 ListLabelsInternal(target, true, resource);
1275 }
1276
1277
1278 virtual void ListAllLabels(std::set<std::string>& target) ORTHANC_OVERRIDE
1279 {
1280 ListLabelsInternal(target, false, -1);
1281 }
1050 }; 1282 };
1051
1052
1053 void OrthancPluginDatabaseV4::CheckSuccess(OrthancPluginErrorCode code) const
1054 {
1055 if (code != OrthancPluginErrorCode_Success)
1056 {
1057 errorDictionary_.LogError(code, true);
1058 throw OrthancException(static_cast<ErrorCode>(code));
1059 }
1060 }
1061 1283
1062 1284
1063 OrthancPluginDatabaseV4::OrthancPluginDatabaseV4(SharedLibrary& library, 1285 OrthancPluginDatabaseV4::OrthancPluginDatabaseV4(SharedLibrary& library,
1064 PluginsErrorDictionary& errorDictionary, 1286 PluginsErrorDictionary& errorDictionary,
1065 const OrthancPluginDatabaseBackendV4* backend, 1287 const _OrthancPluginRegisterDatabaseBackendV4& database,
1066 size_t backendSize,
1067 void* database,
1068 const std::string& serverIdentifier) : 1288 const std::string& serverIdentifier) :
1069 library_(library), 1289 library_(library),
1070 errorDictionary_(errorDictionary), 1290 errorDictionary_(errorDictionary),
1071 database_(database), 1291 definition_(database),
1072 serverIdentifier_(serverIdentifier) 1292 serverIdentifier_(serverIdentifier),
1293 open_(false),
1294 databaseVersion_(0)
1073 { 1295 {
1074 CLOG(INFO, PLUGINS) << "Identifier of this Orthanc server for the global properties " 1296 CLOG(INFO, PLUGINS) << "Identifier of this Orthanc server for the global properties "
1075 << "of the custom database: \"" << serverIdentifier << "\""; 1297 << "of the custom database: \"" << serverIdentifier << "\"";
1076 1298
1077 if (backendSize >= sizeof(backend_)) 1299 if (definition_.backend == NULL ||
1078 { 1300 definition_.operations == NULL ||
1079 memcpy(&backend_, backend, sizeof(backend_)); 1301 definition_.finalize == NULL)
1080 } 1302 {
1081 else 1303 throw OrthancException(ErrorCode_NullPointer);
1082 { 1304 }
1083 // Not all the primitives are implemented by the plugin
1084 memset(&backend_, 0, sizeof(backend_));
1085 memcpy(&backend_, backend, backendSize);
1086 }
1087
1088 // Sanity checks
1089 CHECK_FUNCTION_EXISTS(backend_, readAnswersCount);
1090 CHECK_FUNCTION_EXISTS(backend_, readAnswerAttachment2);
1091 CHECK_FUNCTION_EXISTS(backend_, readAnswerChange);
1092 CHECK_FUNCTION_EXISTS(backend_, readAnswerDicomTag);
1093 CHECK_FUNCTION_EXISTS(backend_, readAnswerExportedResource);
1094 CHECK_FUNCTION_EXISTS(backend_, readAnswerInt32);
1095 CHECK_FUNCTION_EXISTS(backend_, readAnswerInt64);
1096 CHECK_FUNCTION_EXISTS(backend_, readAnswerMatchingResource);
1097 CHECK_FUNCTION_EXISTS(backend_, readAnswerMetadata);
1098 CHECK_FUNCTION_EXISTS(backend_, readAnswerString);
1099
1100 CHECK_FUNCTION_EXISTS(backend_, readEventsCount);
1101 CHECK_FUNCTION_EXISTS(backend_, readEvent2);
1102
1103 CHECK_FUNCTION_EXISTS(backend_, open);
1104 CHECK_FUNCTION_EXISTS(backend_, close);
1105 CHECK_FUNCTION_EXISTS(backend_, destructDatabase);
1106 CHECK_FUNCTION_EXISTS(backend_, getDatabaseVersion);
1107 CHECK_FUNCTION_EXISTS(backend_, upgradeDatabase);
1108 CHECK_FUNCTION_EXISTS(backend_, startTransaction);
1109 CHECK_FUNCTION_EXISTS(backend_, destructTransaction);
1110 CHECK_FUNCTION_EXISTS(backend_, hasRevisionsSupport);
1111 CHECK_FUNCTION_EXISTS(backend_, hasAttachmentCustomDataSupport); // new in v4
1112
1113 CHECK_FUNCTION_EXISTS(backend_, rollback);
1114 CHECK_FUNCTION_EXISTS(backend_, commit);
1115
1116 CHECK_FUNCTION_EXISTS(backend_, addAttachment2);
1117 CHECK_FUNCTION_EXISTS(backend_, clearChanges);
1118 CHECK_FUNCTION_EXISTS(backend_, clearExportedResources);
1119 CHECK_FUNCTION_EXISTS(backend_, clearMainDicomTags);
1120 CHECK_FUNCTION_EXISTS(backend_, createInstance);
1121 CHECK_FUNCTION_EXISTS(backend_, deleteAttachment);
1122 CHECK_FUNCTION_EXISTS(backend_, deleteMetadata);
1123 CHECK_FUNCTION_EXISTS(backend_, deleteResource);
1124 CHECK_FUNCTION_EXISTS(backend_, getAllMetadata);
1125 CHECK_FUNCTION_EXISTS(backend_, getAllPublicIds);
1126 CHECK_FUNCTION_EXISTS(backend_, getAllPublicIdsWithLimit);
1127 CHECK_FUNCTION_EXISTS(backend_, getChanges);
1128 CHECK_FUNCTION_EXISTS(backend_, getChildrenInternalId);
1129 CHECK_FUNCTION_EXISTS(backend_, getChildrenMetadata);
1130 CHECK_FUNCTION_EXISTS(backend_, getChildrenPublicId);
1131 CHECK_FUNCTION_EXISTS(backend_, getExportedResources);
1132 CHECK_FUNCTION_EXISTS(backend_, getLastChange);
1133 CHECK_FUNCTION_EXISTS(backend_, getLastChangeIndex);
1134 CHECK_FUNCTION_EXISTS(backend_, getLastExportedResource);
1135 CHECK_FUNCTION_EXISTS(backend_, getMainDicomTags);
1136 CHECK_FUNCTION_EXISTS(backend_, getPublicId);
1137 CHECK_FUNCTION_EXISTS(backend_, getResourceType);
1138 CHECK_FUNCTION_EXISTS(backend_, getResourcesCount);
1139 CHECK_FUNCTION_EXISTS(backend_, getTotalCompressedSize);
1140 CHECK_FUNCTION_EXISTS(backend_, getTotalUncompressedSize);
1141 CHECK_FUNCTION_EXISTS(backend_, isDiskSizeAbove);
1142 CHECK_FUNCTION_EXISTS(backend_, isExistingResource);
1143 CHECK_FUNCTION_EXISTS(backend_, isProtectedPatient);
1144 CHECK_FUNCTION_EXISTS(backend_, listAvailableAttachments);
1145 CHECK_FUNCTION_EXISTS(backend_, logChange);
1146 CHECK_FUNCTION_EXISTS(backend_, logExportedResource);
1147 CHECK_FUNCTION_EXISTS(backend_, lookupAttachment);
1148 CHECK_FUNCTION_EXISTS(backend_, lookupGlobalProperty);
1149 CHECK_FUNCTION_EXISTS(backend_, lookupMetadata);
1150 CHECK_FUNCTION_EXISTS(backend_, lookupParent);
1151 CHECK_FUNCTION_EXISTS(backend_, lookupResource);
1152 CHECK_FUNCTION_EXISTS(backend_, lookupResourceAndParent);
1153 CHECK_FUNCTION_EXISTS(backend_, lookupResources);
1154 CHECK_FUNCTION_EXISTS(backend_, selectPatientToRecycle);
1155 CHECK_FUNCTION_EXISTS(backend_, selectPatientToRecycle2);
1156 CHECK_FUNCTION_EXISTS(backend_, setGlobalProperty);
1157 CHECK_FUNCTION_EXISTS(backend_, setMetadata);
1158 CHECK_FUNCTION_EXISTS(backend_, setProtectedPatient);
1159 CHECK_FUNCTION_EXISTS(backend_, setResourcesContent);
1160 } 1305 }
1161 1306
1162 1307
1163 OrthancPluginDatabaseV4::~OrthancPluginDatabaseV4() 1308 OrthancPluginDatabaseV4::~OrthancPluginDatabaseV4()
1164 { 1309 {
1165 if (database_ != NULL) 1310 definition_.finalize(definition_.backend);
1166 { 1311 }
1167 OrthancPluginErrorCode code = backend_.destructDatabase(database_); 1312
1168 if (code != OrthancPluginErrorCode_Success) 1313
1169 { 1314 static void AddIdentifierTags(DatabasePluginMessages::Open::Request& request,
1170 // Don't throw exception in destructors 1315 ResourceType level)
1171 errorDictionary_.LogError(code, true); 1316 {
1172 } 1317 const DicomTag* tags = NULL;
1318 size_t size;
1319
1320 ServerToolbox::LoadIdentifiers(tags, size, level);
1321
1322 if (tags == NULL ||
1323 size == 0)
1324 {
1325 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1326 }
1327
1328 for (size_t i = 0; i < size; i++)
1329 {
1330 DatabasePluginMessages::Open_Request_IdentifierTag* tag = request.add_identifier_tags();
1331 tag->set_level(Convert(level));
1332 tag->set_group(tags[i].GetGroup());
1333 tag->set_element(tags[i].GetElement());
1334 tag->set_name(FromDcmtkBridge::GetTagName(tags[i], ""));
1173 } 1335 }
1174 } 1336 }
1175 1337
1176 1338
1177 void OrthancPluginDatabaseV4::Open() 1339 void OrthancPluginDatabaseV4::Open()
1178 { 1340 {
1179 CheckSuccess(backend_.open(database_)); 1341 if (open_)
1342 {
1343 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1344 }
1345
1346 {
1347 DatabasePluginMessages::DatabaseRequest request;
1348 AddIdentifierTags(*request.mutable_open(), ResourceType_Patient);
1349 AddIdentifierTags(*request.mutable_open(), ResourceType_Study);
1350 AddIdentifierTags(*request.mutable_open(), ResourceType_Series);
1351 AddIdentifierTags(*request.mutable_open(), ResourceType_Instance);
1352
1353 DatabasePluginMessages::DatabaseResponse response;
1354 ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_OPEN, request);
1355 }
1356
1357 {
1358 DatabasePluginMessages::DatabaseRequest request;
1359 DatabasePluginMessages::DatabaseResponse response;
1360 ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION, request);
1361
1362 const ::Orthanc::DatabasePluginMessages::GetSystemInformation_Response& systemInfo = response.get_system_information();
1363 databaseVersion_ = systemInfo.database_version();
1364 dbCapabilities_.SetFlushToDisk(systemInfo.supports_flush_to_disk());
1365 dbCapabilities_.SetRevisionsSupport(systemInfo.supports_revisions());
1366 dbCapabilities_.SetLabelsSupport(systemInfo.supports_labels());
1367 dbCapabilities_.SetAtomicIncrementGlobalProperty(systemInfo.supports_increment_global_property());
1368 dbCapabilities_.SetUpdateAndGetStatistics(systemInfo.has_update_and_get_statistics());
1369 dbCapabilities_.SetMeasureLatency(systemInfo.has_measure_latency());
1370 }
1371
1372 open_ = true;
1180 } 1373 }
1181 1374
1182 1375
1183 void OrthancPluginDatabaseV4::Close() 1376 void OrthancPluginDatabaseV4::Close()
1184 { 1377 {
1185 CheckSuccess(backend_.close(database_)); 1378 if (!open_)
1379 {
1380 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1381 }
1382 else
1383 {
1384 DatabasePluginMessages::DatabaseRequest request;
1385 DatabasePluginMessages::DatabaseResponse response;
1386 ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_CLOSE, request);
1387 }
1388 }
1389
1390
1391
1392 void OrthancPluginDatabaseV4::FlushToDisk()
1393 {
1394 if (!open_ ||
1395 !GetDatabaseCapabilities().HasFlushToDisk())
1396 {
1397 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1398 }
1399 else
1400 {
1401 DatabasePluginMessages::DatabaseRequest request;
1402 DatabasePluginMessages::DatabaseResponse response;
1403 ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_FLUSH_TO_DISK, request);
1404 }
1186 } 1405 }
1187 1406
1188 1407
1189 IDatabaseWrapper::ITransaction* OrthancPluginDatabaseV4::StartTransaction(TransactionType type, 1408 IDatabaseWrapper::ITransaction* OrthancPluginDatabaseV4::StartTransaction(TransactionType type,
1190 IDatabaseListener& listener) 1409 IDatabaseListener& listener)
1191 { 1410 {
1192 switch (type) 1411 if (!open_)
1193 { 1412 {
1194 case TransactionType_ReadOnly: 1413 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1195 return new Transaction(*this, listener, OrthancPluginDatabaseTransactionType_ReadOnly); 1414 }
1196 1415 else
1197 case TransactionType_ReadWrite: 1416 {
1198 return new Transaction(*this, listener, OrthancPluginDatabaseTransactionType_ReadWrite); 1417 return new Transaction(*this, listener, type);
1199
1200 default:
1201 throw OrthancException(ErrorCode_InternalError);
1202 } 1418 }
1203 } 1419 }
1204 1420
1205 1421
1206 unsigned int OrthancPluginDatabaseV4::GetDatabaseVersion() 1422 unsigned int OrthancPluginDatabaseV4::GetDatabaseVersion()
1207 { 1423 {
1208 uint32_t version = 0; 1424 if (!open_)
1209 CheckSuccess(backend_.getDatabaseVersion(database_, &version)); 1425 {
1210 return version; 1426 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1427 }
1428 else
1429 {
1430 return databaseVersion_;
1431 }
1211 } 1432 }
1212 1433
1213 1434
1214 void OrthancPluginDatabaseV4::Upgrade(unsigned int targetVersion, 1435 void OrthancPluginDatabaseV4::Upgrade(unsigned int targetVersion,
1215 IStorageArea& storageArea) 1436 IStorageArea& storageArea)
1216 { 1437 {
1217 VoidDatabaseListener listener; 1438 if (!open_)
1218 1439 {
1219 if (backend_.upgradeDatabase != NULL) 1440 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1220 { 1441 }
1221 Transaction transaction(*this, listener, OrthancPluginDatabaseTransactionType_ReadWrite); 1442 else
1222 1443 {
1223 OrthancPluginErrorCode code = backend_.upgradeDatabase( 1444 VoidDatabaseListener listener;
1224 database_, reinterpret_cast<OrthancPluginStorageArea*>(&storageArea), 1445 Transaction transaction(*this, listener, TransactionType_ReadWrite);
1225 static_cast<uint32_t>(targetVersion)); 1446
1226 1447 try
1227 if (code == OrthancPluginErrorCode_Success) 1448 {
1228 { 1449 DatabasePluginMessages::DatabaseRequest request;
1450 request.mutable_upgrade()->set_target_version(targetVersion);
1451 request.mutable_upgrade()->set_storage_area(reinterpret_cast<intptr_t>(&storageArea));
1452 request.mutable_upgrade()->set_transaction(reinterpret_cast<intptr_t>(transaction.GetTransactionObject()));
1453
1454 DatabasePluginMessages::DatabaseResponse response;
1455
1456 ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_UPGRADE, request);
1229 transaction.Commit(0); 1457 transaction.Commit(0);
1230 } 1458 }
1231 else 1459 catch (OrthancException& e)
1232 { 1460 {
1233 transaction.Rollback(); 1461 transaction.Rollback();
1234 errorDictionary_.LogError(code, true); 1462 throw;
1235 throw OrthancException(static_cast<ErrorCode>(code)); 1463 }
1236 } 1464 }
1237 } 1465 }
1238 } 1466
1239 1467
1240 1468 uint64_t OrthancPluginDatabaseV4::MeasureLatency()
1241 bool OrthancPluginDatabaseV4::HasRevisionsSupport() const 1469 {
1242 { 1470 if (!open_)
1243 // WARNING: This method requires "Open()" to have been called 1471 {
1244 uint8_t hasRevisions; 1472 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1245 CheckSuccess(backend_.hasRevisionsSupport(database_, &hasRevisions)); 1473 }
1246 return (hasRevisions != 0); 1474 else
1247 } 1475 {
1248 1476 DatabasePluginMessages::DatabaseRequest request;
1249 bool OrthancPluginDatabaseV4::HasAttachmentCustomDataSupport() const 1477 DatabasePluginMessages::DatabaseResponse response;
1250 { 1478
1251 // WARNING: This method requires "Open()" to have been called 1479 ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_MEASURE_LATENCY, request);
1252 uint8_t hasAttachmentCustomDataSupport; 1480 return response.measure_latency().latency_us();
1253 CheckSuccess(backend_.hasAttachmentCustomDataSupport(database_, &hasAttachmentCustomDataSupport)); 1481 }
1254 return (hasAttachmentCustomDataSupport != 0); 1482 }
1483
1484
1485 const IDatabaseWrapper::Capabilities OrthancPluginDatabaseV4::GetDatabaseCapabilities() const
1486 {
1487 if (!open_)
1488 {
1489 throw OrthancException(ErrorCode_BadSequenceOfCalls);
1490 }
1491 else
1492 {
1493 return dbCapabilities_;
1494 }
1255 } 1495 }
1256 } 1496 }