comparison OrthancServer/Resources/Graveyard/DatabasePluginSample/Database.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Resources/Graveyard/DatabasePluginSample/Database.cpp@94f4a18a79cc
children cd363608551a
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "Database.h"
35
36 #include "../../../Core/DicomFormat/DicomArray.h"
37
38 #include <EmbeddedResources.h>
39 #include <boost/lexical_cast.hpp>
40
41
42 namespace Internals
43 {
44 class SignalFileDeleted : public Orthanc::SQLite::IScalarFunction
45 {
46 private:
47 OrthancPlugins::DatabaseBackendOutput& output_;
48
49 public:
50 SignalFileDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output)
51 {
52 }
53
54 virtual const char* GetName() const
55 {
56 return "SignalFileDeleted";
57 }
58
59 virtual unsigned int GetCardinality() const
60 {
61 return 7;
62 }
63
64 virtual void Compute(Orthanc::SQLite::FunctionContext& context)
65 {
66 std::string uncompressedMD5, compressedMD5;
67
68 if (!context.IsNullValue(5))
69 {
70 uncompressedMD5 = context.GetStringValue(5);
71 }
72
73 if (!context.IsNullValue(6))
74 {
75 compressedMD5 = context.GetStringValue(6);
76 }
77
78 output_.SignalDeletedAttachment(context.GetStringValue(0),
79 context.GetIntValue(1),
80 context.GetInt64Value(2),
81 uncompressedMD5,
82 context.GetIntValue(3),
83 context.GetInt64Value(4),
84 compressedMD5);
85 }
86 };
87
88
89 class SignalResourceDeleted : public Orthanc::SQLite::IScalarFunction
90 {
91 private:
92 OrthancPlugins::DatabaseBackendOutput& output_;
93
94 public:
95 SignalResourceDeleted(OrthancPlugins::DatabaseBackendOutput& output) : output_(output)
96 {
97 }
98
99 virtual const char* GetName() const
100 {
101 return "SignalResourceDeleted";
102 }
103
104 virtual unsigned int GetCardinality() const
105 {
106 return 2;
107 }
108
109 virtual void Compute(Orthanc::SQLite::FunctionContext& context)
110 {
111 output_.SignalDeletedResource(context.GetStringValue(0),
112 Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1))));
113 }
114 };
115 }
116
117
118 class Database::SignalRemainingAncestor : public Orthanc::SQLite::IScalarFunction
119 {
120 private:
121 bool hasRemainingAncestor_;
122 std::string remainingPublicId_;
123 OrthancPluginResourceType remainingType_;
124
125 public:
126 SignalRemainingAncestor() :
127 hasRemainingAncestor_(false),
128 remainingType_(OrthancPluginResourceType_Instance) // Some dummy value
129 {
130 }
131
132 void Reset()
133 {
134 hasRemainingAncestor_ = false;
135 }
136
137 virtual const char* GetName() const
138 {
139 return "SignalRemainingAncestor";
140 }
141
142 virtual unsigned int GetCardinality() const
143 {
144 return 2;
145 }
146
147 virtual void Compute(Orthanc::SQLite::FunctionContext& context)
148 {
149 if (!hasRemainingAncestor_ ||
150 remainingType_ >= context.GetIntValue(1))
151 {
152 hasRemainingAncestor_ = true;
153 remainingPublicId_ = context.GetStringValue(0);
154 remainingType_ = Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1)));
155 }
156 }
157
158 bool HasRemainingAncestor() const
159 {
160 return hasRemainingAncestor_;
161 }
162
163 const std::string& GetRemainingAncestorId() const
164 {
165 assert(hasRemainingAncestor_);
166 return remainingPublicId_;
167 }
168
169 OrthancPluginResourceType GetRemainingAncestorType() const
170 {
171 assert(hasRemainingAncestor_);
172 return remainingType_;
173 }
174 };
175
176
177
178 Database::Database(const std::string& path) :
179 path_(path),
180 base_(db_),
181 signalRemainingAncestor_(NULL)
182 {
183 }
184
185
186 void Database::Open()
187 {
188 db_.Open(path_);
189
190 db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
191
192 // http://www.sqlite.org/pragma.html
193 db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
194 db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
195 db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
196 db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
197 //db_.Execute("PRAGMA TEMP_STORE=memory");
198
199 if (!db_.DoesTableExist("GlobalProperties"))
200 {
201 std::string query;
202 Orthanc::EmbeddedResources::GetFileResource(query, Orthanc::EmbeddedResources::PREPARE_DATABASE);
203 db_.Execute(query);
204 }
205
206 signalRemainingAncestor_ = new SignalRemainingAncestor;
207 db_.Register(signalRemainingAncestor_);
208 db_.Register(new Internals::SignalFileDeleted(GetOutput()));
209 db_.Register(new Internals::SignalResourceDeleted(GetOutput()));
210 }
211
212
213 void Database::Close()
214 {
215 db_.Close();
216 }
217
218
219 void Database::AddAttachment(int64_t id,
220 const OrthancPluginAttachment& attachment)
221 {
222 Orthanc::FileInfo info(attachment.uuid,
223 static_cast<Orthanc::FileContentType>(attachment.contentType),
224 attachment.uncompressedSize,
225 attachment.uncompressedHash,
226 static_cast<Orthanc::CompressionType>(attachment.compressionType),
227 attachment.compressedSize,
228 attachment.compressedHash);
229 base_.AddAttachment(id, info);
230 }
231
232
233 void Database::DeleteResource(int64_t id)
234 {
235 signalRemainingAncestor_->Reset();
236
237 Orthanc::SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
238 s.BindInt64(0, id);
239 s.Run();
240
241 if (signalRemainingAncestor_->HasRemainingAncestor())
242 {
243 GetOutput().SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorId(),
244 signalRemainingAncestor_->GetRemainingAncestorType());
245 }
246 }
247
248
249 static void Answer(OrthancPlugins::DatabaseBackendOutput& output,
250 const Orthanc::ServerIndexChange& change)
251 {
252 output.AnswerChange(change.GetSeq(),
253 change.GetChangeType(),
254 Orthanc::Plugins::Convert(change.GetResourceType()),
255 change.GetPublicId(),
256 change.GetDate());
257 }
258
259
260 static void Answer(OrthancPlugins::DatabaseBackendOutput& output,
261 const Orthanc::ExportedResource& resource)
262 {
263 output.AnswerExportedResource(resource.GetSeq(),
264 Orthanc::Plugins::Convert(resource.GetResourceType()),
265 resource.GetPublicId(),
266 resource.GetModality(),
267 resource.GetDate(),
268 resource.GetPatientId(),
269 resource.GetStudyInstanceUid(),
270 resource.GetSeriesInstanceUid(),
271 resource.GetSopInstanceUid());
272 }
273
274
275 void Database::GetChanges(bool& done /*out*/,
276 int64_t since,
277 uint32_t maxResults)
278 {
279 typedef std::list<Orthanc::ServerIndexChange> Changes;
280
281 Changes changes;
282 base_.GetChanges(changes, done, since, maxResults);
283
284 for (Changes::const_iterator it = changes.begin(); it != changes.end(); ++it)
285 {
286 Answer(GetOutput(), *it);
287 }
288 }
289
290
291 void Database::GetExportedResources(bool& done /*out*/,
292 int64_t since,
293 uint32_t maxResults)
294 {
295 typedef std::list<Orthanc::ExportedResource> Resources;
296
297 Resources resources;
298 base_.GetExportedResources(resources, done, since, maxResults);
299
300 for (Resources::const_iterator it = resources.begin(); it != resources.end(); ++it)
301 {
302 Answer(GetOutput(), *it);
303 }
304 }
305
306
307 void Database::GetLastChange()
308 {
309 std::list<Orthanc::ServerIndexChange> change;
310 Orthanc::ErrorCode code = base_.GetLastChange(change);
311
312 if (code != Orthanc::ErrorCode_Success)
313 {
314 throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
315 }
316
317 if (!change.empty())
318 {
319 Answer(GetOutput(), change.front());
320 }
321 }
322
323
324 void Database::GetLastExportedResource()
325 {
326 std::list<Orthanc::ExportedResource> resource;
327 base_.GetLastExportedResource(resource);
328
329 if (!resource.empty())
330 {
331 Answer(GetOutput(), resource.front());
332 }
333 }
334
335
336 void Database::GetMainDicomTags(int64_t id)
337 {
338 Orthanc::DicomMap tags;
339 base_.GetMainDicomTags(tags, id);
340
341 Orthanc::DicomArray arr(tags);
342 for (size_t i = 0; i < arr.GetSize(); i++)
343 {
344 GetOutput().AnswerDicomTag(arr.GetElement(i).GetTag().GetGroup(),
345 arr.GetElement(i).GetTag().GetElement(),
346 arr.GetElement(i).GetValue().GetContent());
347 }
348 }
349
350
351 std::string Database::GetPublicId(int64_t resourceId)
352 {
353 std::string id;
354 if (base_.GetPublicId(id, resourceId))
355 {
356 return id;
357 }
358 else
359 {
360 throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_UnknownResource);
361 }
362 }
363
364
365 OrthancPluginResourceType Database::GetResourceType(int64_t resourceId)
366 {
367 Orthanc::ResourceType result;
368 Orthanc::ErrorCode code = base_.GetResourceType(result, resourceId);
369
370 if (code == Orthanc::ErrorCode_Success)
371 {
372 return Orthanc::Plugins::Convert(result);
373 }
374 else
375 {
376 throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
377 }
378 }
379
380
381
382 template <typename I>
383 static void ConvertList(std::list<int32_t>& target,
384 const std::list<I>& source)
385 {
386 for (typename std::list<I>::const_iterator
387 it = source.begin(); it != source.end(); ++it)
388 {
389 target.push_back(*it);
390 }
391 }
392
393
394 void Database::ListAvailableMetadata(std::list<int32_t>& target /*out*/,
395 int64_t id)
396 {
397 std::list<Orthanc::MetadataType> tmp;
398 base_.ListAvailableMetadata(tmp, id);
399 ConvertList(target, tmp);
400 }
401
402
403 void Database::ListAvailableAttachments(std::list<int32_t>& target /*out*/,
404 int64_t id)
405 {
406 std::list<Orthanc::FileContentType> tmp;
407 base_.ListAvailableAttachments(tmp, id);
408 ConvertList(target, tmp);
409 }
410
411
412 void Database::LogChange(const OrthancPluginChange& change)
413 {
414 int64_t id;
415 OrthancPluginResourceType type;
416 if (!LookupResource(id, type, change.publicId) ||
417 type != change.resourceType)
418 {
419 throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_DatabasePlugin);
420 }
421
422 Orthanc::ServerIndexChange tmp(change.seq,
423 static_cast<Orthanc::ChangeType>(change.changeType),
424 Orthanc::Plugins::Convert(change.resourceType),
425 change.publicId,
426 change.date);
427
428 base_.LogChange(id, tmp);
429 }
430
431
432 void Database::LogExportedResource(const OrthancPluginExportedResource& resource)
433 {
434 Orthanc::ExportedResource tmp(resource.seq,
435 Orthanc::Plugins::Convert(resource.resourceType),
436 resource.publicId,
437 resource.modality,
438 resource.date,
439 resource.patientId,
440 resource.studyInstanceUid,
441 resource.seriesInstanceUid,
442 resource.sopInstanceUid);
443
444 base_.LogExportedResource(tmp);
445 }
446
447
448 bool Database::LookupAttachment(int64_t id,
449 int32_t contentType)
450 {
451 Orthanc::FileInfo attachment;
452 if (base_.LookupAttachment(attachment, id, static_cast<Orthanc::FileContentType>(contentType)))
453 {
454 GetOutput().AnswerAttachment(attachment.GetUuid(),
455 attachment.GetContentType(),
456 attachment.GetUncompressedSize(),
457 attachment.GetUncompressedMD5(),
458 attachment.GetCompressionType(),
459 attachment.GetCompressedSize(),
460 attachment.GetCompressedMD5());
461 return true;
462 }
463 else
464 {
465 return false;
466 }
467 }
468
469
470 bool Database::LookupParent(int64_t& parentId /*out*/,
471 int64_t resourceId)
472 {
473 bool found;
474 Orthanc::ErrorCode code = base_.LookupParent(found, parentId, resourceId);
475
476 if (code == Orthanc::ErrorCode_Success)
477 {
478 return found;
479 }
480 else
481 {
482 throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
483 }
484 }
485
486
487 bool Database::LookupResource(int64_t& id /*out*/,
488 OrthancPluginResourceType& type /*out*/,
489 const char* publicId)
490 {
491 Orthanc::ResourceType tmp;
492 if (base_.LookupResource(id, tmp, publicId))
493 {
494 type = Orthanc::Plugins::Convert(tmp);
495 return true;
496 }
497 else
498 {
499 return false;
500 }
501 }
502
503
504 void Database::StartTransaction()
505 {
506 transaction_.reset(new Orthanc::SQLite::Transaction(db_));
507 transaction_->Begin();
508 }
509
510
511 void Database::RollbackTransaction()
512 {
513 transaction_->Rollback();
514 transaction_.reset(NULL);
515 }
516
517
518 void Database::CommitTransaction()
519 {
520 transaction_->Commit();
521 transaction_.reset(NULL);
522 }
523
524
525 uint32_t Database::GetDatabaseVersion()
526 {
527 std::string version;
528
529 if (!LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion))
530 {
531 throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError);
532 }
533
534 try
535 {
536 return boost::lexical_cast<uint32_t>(version);
537 }
538 catch (boost::bad_lexical_cast&)
539 {
540 throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError);
541 }
542 }
543
544
545 void Database::UpgradeDatabase(uint32_t targetVersion,
546 OrthancPluginStorageArea* storageArea)
547 {
548 if (targetVersion == 6)
549 {
550 OrthancPluginErrorCode code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea,
551 OrthancPluginResourceType_Study);
552 if (code == OrthancPluginErrorCode_Success)
553 {
554 code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea,
555 OrthancPluginResourceType_Series);
556 }
557
558 if (code != OrthancPluginErrorCode_Success)
559 {
560 throw OrthancPlugins::DatabaseException(code);
561 }
562
563 base_.SetGlobalProperty(Orthanc::GlobalProperty_DatabaseSchemaVersion, "6");
564 }
565 }