comparison PalantirServer/ServerIndex.cpp @ 0:3959d33612cc

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 19 Jul 2012 14:32:22 +0200
parents
children 67a6978503b7
comparison
equal deleted inserted replaced
-1:000000000000 0:3959d33612cc
1 /**
2 * Palantir - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
4 * Belgium
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 **/
19
20
21 #include "ServerIndex.h"
22
23 using namespace Palantir;
24
25 #include <EmbeddedResources.h>
26 #include "../Core/Toolbox.h"
27 #include "../Core/Uuid.h"
28 #include "../Core/DicomFormat/DicomArray.h"
29 #include "../Core/SQLite/Transaction.h"
30 #include "FromDcmtkBridge.h"
31
32 #include <boost/lexical_cast.hpp>
33 #include <stdio.h>
34
35 namespace Palantir
36 {
37 namespace Internals
38 {
39 class DeleteFromFileStorageFunction : public SQLite::IScalarFunction
40 {
41 private:
42 FileStorage fileStorage_;
43
44 public:
45 DeleteFromFileStorageFunction(const std::string& path) :
46 fileStorage_(path)
47 {
48 }
49
50 virtual const char* GetName() const
51 {
52 return "DeleteFromFileStorage";
53 }
54
55 virtual unsigned int GetCardinality() const
56 {
57 return 1;
58 }
59
60 virtual void Compute(SQLite::FunctionContext& context)
61 {
62 std::string fileUuid = context.GetStringValue(0);
63 printf("Removing file [%s]\n", fileUuid.c_str());
64 if (Toolbox::IsUuid(fileUuid))
65 {
66 fileStorage_.Remove(fileUuid);
67 }
68 }
69 };
70
71
72 class SignalDeletedLevelFunction : public SQLite::IScalarFunction
73 {
74 private:
75 int remainingLevel_;
76 std::string remainingLevelUuid_;
77
78 public:
79 void Clear()
80 {
81 remainingLevel_ = std::numeric_limits<int>::max();
82 }
83
84 bool HasRemainingLevel() const
85 {
86 return (remainingLevel_ != 0 &&
87 remainingLevel_ != std::numeric_limits<int>::max());
88 }
89
90 const std::string& GetRemainingLevelUuid() const
91 {
92 assert(HasRemainingLevel());
93 return remainingLevelUuid_;
94 }
95
96 const char* GetRemainingLevelType() const
97 {
98 assert(HasRemainingLevel());
99 switch (remainingLevel_)
100 {
101 case 1:
102 return "patient";
103 case 2:
104 return "study";
105 case 3:
106 return "series";
107 default:
108 throw PalantirException(ErrorCode_InternalError);
109 }
110 }
111
112 virtual const char* GetName() const
113 {
114 return "SignalDeletedLevel";
115 }
116
117 virtual unsigned int GetCardinality() const
118 {
119 return 2;
120 }
121
122 virtual void Compute(SQLite::FunctionContext& context)
123 {
124 int level = context.GetIntValue(0);
125 if (level < remainingLevel_)
126 {
127 remainingLevel_ = level;
128 remainingLevelUuid_ = context.GetStringValue(1);
129 }
130
131 //printf("deleted level [%d] [%s]\n", level, context.GetStringValue(1).c_str());
132 }
133 };
134 }
135
136
137 void ServerIndex::StoreMainDicomTags(const std::string& uuid,
138 const DicomMap& map)
139 {
140 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
141
142 DicomArray flattened(map);
143 for (size_t i = 0; i < flattened.GetSize(); i++)
144 {
145 s.Reset();
146 s.BindString(0, uuid);
147 s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup());
148 s.BindInt(2, flattened.GetElement(i).GetTag().GetElement());
149 s.BindString(3, flattened.GetElement(i).GetValue().AsString());
150 s.Run();
151 }
152 }
153
154 bool ServerIndex::GetMainDicomStringTag(std::string& result,
155 const std::string& uuid,
156 const DicomTag& tag)
157 {
158 SQLite::Statement s(db_, SQLITE_FROM_HERE,
159 "SELECT * FROM MainDicomTags WHERE uuid=? AND tagGroup=? AND tagElement=?");
160 s.BindString(0, uuid);
161 s.BindInt(1, tag.GetGroup());
162 s.BindInt(2, tag.GetElement());
163 if (!s.Step())
164 {
165 return false;
166 }
167
168 result = s.ColumnString(0);
169 return true;
170 }
171
172 bool ServerIndex::GetMainDicomIntTag(int& result,
173 const std::string& uuid,
174 const DicomTag& tag)
175 {
176 std::string s;
177 if (!GetMainDicomStringTag(s, uuid, tag))
178 {
179 return false;
180 }
181
182 try
183 {
184 result = boost::lexical_cast<int>(s);
185 return true;
186 }
187 catch (boost::bad_lexical_cast)
188 {
189 return false;
190 }
191 }
192
193 bool ServerIndex::HasInstance(std::string& instanceUuid,
194 const std::string& dicomInstance)
195 {
196 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Instances WHERE dicomInstance=?");
197 s.BindString(0, dicomInstance);
198 if (s.Step())
199 {
200 instanceUuid = s.ColumnString(0);
201 return true;
202 }
203 else
204 {
205 return false;
206 }
207 }
208
209
210 void ServerIndex::RecordChange(const std::string& resourceType,
211 const std::string& uuid)
212 {
213 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?)");
214 s.BindString(0, resourceType);
215 s.BindString(1, uuid);
216 s.Run();
217 }
218
219
220 std::string ServerIndex::CreateInstance(const std::string& parentSeriesUuid,
221 const std::string& dicomInstance,
222 const DicomMap& dicomSummary,
223 const std::string& fileUuid,
224 uint64_t fileSize,
225 const std::string& jsonUuid,
226 const std::string& distantAet)
227 {
228 std::string instanceUuid = Toolbox::GenerateUuid();
229
230 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Instances VALUES(?, ?, ?, ?, ?, ?, ?)");
231 s.BindString(0, instanceUuid);
232 s.BindString(1, parentSeriesUuid);
233 s.BindString(2, dicomInstance);
234 s.BindString(3, fileUuid);
235 s.BindInt64(4, fileSize);
236 s.BindString(5, jsonUuid);
237 s.BindString(6, distantAet);
238 s.Run();
239
240 RecordChange("instances", instanceUuid);
241
242 DicomMap dicom;
243 dicomSummary.ExtractInstanceInformation(dicom);
244 dicom.Remove(DicomTag::INSTANCE_UID);
245 StoreMainDicomTags(instanceUuid, dicom);
246
247 return instanceUuid;
248 }
249
250 void ServerIndex::RemoveInstance(const std::string& uuid)
251 {
252 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Instances WHERE uuid=?");
253 s.BindString(0, uuid);
254 s.Run();
255 }
256
257 bool ServerIndex::HasSeries(std::string& seriesUuid,
258 const std::string& dicomSeries)
259 {
260 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Series WHERE dicomSeries=?");
261 s.BindString(0, dicomSeries);
262 if (s.Step())
263 {
264 seriesUuid = s.ColumnString(0);
265 return true;
266 }
267 else
268 {
269 return false;
270 }
271 }
272
273 std::string ServerIndex::CreateSeries(const std::string& parentStudyUuid,
274 const std::string& dicomSeries,
275 const DicomMap& dicomSummary)
276 {
277 std::string seriesUuid = Toolbox::GenerateUuid();
278
279 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Series VALUES(?, ?, ?)");
280 s.BindString(0, seriesUuid);
281 s.BindString(1, parentStudyUuid);
282 s.BindString(2, dicomSeries);
283 s.Run();
284
285 RecordChange("series", seriesUuid);
286
287 DicomMap dicom;
288 dicomSummary.ExtractSeriesInformation(dicom);
289 dicom.Remove(DicomTag::SERIES_UID);
290 StoreMainDicomTags(seriesUuid, dicom);
291
292 return seriesUuid;
293 }
294
295 bool ServerIndex::HasStudy(std::string& studyUuid,
296 const std::string& dicomStudy)
297 {
298 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Studies WHERE dicomStudy=?");
299 s.BindString(0, dicomStudy);
300 if (s.Step())
301 {
302 studyUuid = s.ColumnString(0);
303 return true;
304 }
305 else
306 {
307 return false;
308 }
309 }
310
311 std::string ServerIndex::CreateStudy(const std::string& parentPatientUuid,
312 const std::string& dicomStudy,
313 const DicomMap& dicomSummary)
314 {
315 std::string studyUuid = Toolbox::GenerateUuid();
316
317 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Studies VALUES(?, ?, ?)");
318 s.BindString(0, studyUuid);
319 s.BindString(1, parentPatientUuid);
320 s.BindString(2, dicomStudy);
321 s.Run();
322
323 RecordChange("studies", studyUuid);
324
325 DicomMap dicom;
326 dicomSummary.ExtractStudyInformation(dicom);
327 dicom.Remove(DicomTag::STUDY_UID);
328 StoreMainDicomTags(studyUuid, dicom);
329
330 return studyUuid;
331 }
332
333
334
335 bool ServerIndex::HasPatient(std::string& patientUuid,
336 const std::string& dicomPatientId)
337 {
338 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Patients WHERE dicomPatientId=?");
339 s.BindString(0, dicomPatientId);
340 if (s.Step())
341 {
342 patientUuid = s.ColumnString(0);
343 return true;
344 }
345 else
346 {
347 return false;
348 }
349 }
350
351 std::string ServerIndex::CreatePatient(const std::string& patientId,
352 const DicomMap& dicomSummary)
353 {
354 std::string patientUuid = Toolbox::GenerateUuid();
355 std::string dicomPatientId = dicomSummary.GetValue(DicomTag::PATIENT_ID).AsString();
356
357 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Patients VALUES(?, ?)");
358 s.BindString(0, patientUuid);
359 s.BindString(1, dicomPatientId);
360 s.Run();
361
362 RecordChange("patients", patientUuid);
363
364 DicomMap dicom;
365 dicomSummary.ExtractPatientInformation(dicom);
366 dicom.Remove(DicomTag::PATIENT_ID);
367 StoreMainDicomTags(patientUuid, dicom);
368
369 return patientUuid;
370 }
371
372
373 void ServerIndex::GetMainDicomTags(DicomMap& map,
374 const std::string& uuid)
375 {
376 map.Clear();
377
378 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE uuid=?");
379 s.BindString(0, uuid);
380 while (s.Step())
381 {
382 map.SetValue(s.ColumnInt(1),
383 s.ColumnInt(2),
384 s.ColumnString(3));
385 }
386 }
387
388 void ServerIndex::MainDicomTagsToJson(Json::Value& target,
389 const std::string& uuid)
390 {
391 DicomMap map;
392 GetMainDicomTags(map, uuid);
393 target["MainDicomTags"] = Json::objectValue;
394 FromDcmtkBridge::ToJson(target["MainDicomTags"], map);
395 }
396
397
398 bool ServerIndex::DeleteInternal(Json::Value& target,
399 const std::string& uuid,
400 const std::string& tableName)
401 {
402 boost::mutex::scoped_lock scoped_lock(mutex_);
403
404 deletedLevels_->Clear();
405
406 SQLite::Statement s(db_, "DELETE FROM " + tableName + " WHERE uuid=?");
407 s.BindString(0, uuid);
408
409 if (!s.Run())
410 {
411 return false;
412 }
413
414 if (db_.GetLastChangeCount() == 0)
415 {
416 // Nothing was deleted, inexistent UUID
417 return false;
418 }
419
420 if (deletedLevels_->HasRemainingLevel())
421 {
422 std::string type(deletedLevels_->GetRemainingLevelType());
423 const std::string& uuid = deletedLevels_->GetRemainingLevelUuid();
424
425 target["RemainingAncestor"] = Json::Value(Json::objectValue);
426 target["RemainingAncestor"]["Path"] = "/" + type + "/" + uuid;
427 target["RemainingAncestor"]["Type"] = type;
428 target["RemainingAncestor"]["ID"] = uuid;
429 }
430 else
431 {
432 target["RemainingAncestor"] = Json::nullValue;
433 }
434
435 return true;
436 }
437
438
439 ServerIndex::ServerIndex(const std::string& storagePath)
440 {
441 boost::filesystem::path p = storagePath;
442
443 try
444 {
445 boost::filesystem::create_directories(storagePath);
446 }
447 catch (boost::filesystem::filesystem_error)
448 {
449 }
450
451 p /= "index";
452 db_.Open(p.string());
453 db_.Register(new Internals::DeleteFromFileStorageFunction(storagePath));
454 deletedLevels_ = (Internals::SignalDeletedLevelFunction*)
455 db_.Register(new Internals::SignalDeletedLevelFunction);
456
457 if (!db_.DoesTableExist("GlobalProperties"))
458 {
459 printf("Creating the database\n");
460 std::string query;
461 EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE);
462 db_.Execute(query);
463 }
464 }
465
466
467 StoreStatus ServerIndex::Store(std::string& instanceUuid,
468 const DicomMap& dicomSummary,
469 const std::string& fileUuid,
470 uint64_t uncompressedFileSize,
471 const std::string& jsonUuid,
472 const std::string& distantAet)
473 {
474 boost::mutex::scoped_lock scoped_lock(mutex_);
475
476 std::string dicomPatientId = dicomSummary.GetValue(DicomTag::PATIENT_ID).AsString();
477 std::string dicomInstance = dicomSummary.GetValue(DicomTag::INSTANCE_UID).AsString();
478 std::string dicomSeries = dicomSummary.GetValue(DicomTag::SERIES_UID).AsString();
479 std::string dicomStudy = dicomSummary.GetValue(DicomTag::STUDY_UID).AsString();
480
481 try
482 {
483 SQLite::Transaction t(db_);
484 t.Begin();
485
486 if (HasInstance(instanceUuid, dicomInstance))
487 {
488 return StoreStatus_AlreadyStored;
489 // TODO: Check consistency?
490 }
491
492 std::string patientUuid;
493 if (HasPatient(patientUuid, dicomPatientId))
494 {
495 // TODO: Check consistency?
496 }
497 else
498 {
499 patientUuid = CreatePatient(dicomPatientId, dicomSummary);
500 }
501
502 std::string studyUuid;
503 if (HasStudy(studyUuid, dicomStudy))
504 {
505 // TODO: Check consistency?
506 }
507 else
508 {
509 studyUuid = CreateStudy(patientUuid, dicomStudy, dicomSummary);
510 }
511
512 std::string seriesUuid;
513 if (HasSeries(seriesUuid, dicomSeries))
514 {
515 // TODO: Check consistency?
516 }
517 else
518 {
519 seriesUuid = CreateSeries(studyUuid, dicomSeries, dicomSummary);
520 }
521
522 instanceUuid = CreateInstance(seriesUuid, dicomInstance, dicomSummary, fileUuid,
523 uncompressedFileSize, jsonUuid, distantAet);
524
525 t.Commit();
526 return StoreStatus_Success;
527 //t.Rollback();
528 }
529 catch (PalantirException& e)
530 {
531 std::cout << "EXCEPT2 [" << e.What() << "]" << " " << db_.GetErrorMessage() << std::endl;
532 }
533
534 return StoreStatus_Failure;
535 }
536
537
538 StoreStatus ServerIndex::Store(std::string& instanceUuid,
539 FileStorage& storage,
540 const char* dicomFile,
541 size_t dicomSize,
542 const DicomMap& dicomSummary,
543 const Json::Value& dicomJson,
544 const std::string& distantAet)
545 {
546 std::string fileUuid = storage.Create(dicomFile, dicomSize);
547 std::string jsonUuid = storage.Create(dicomJson.toStyledString());
548 StoreStatus status = Store(instanceUuid, dicomSummary, fileUuid,
549 dicomSize, jsonUuid, distantAet);
550
551 if (status != StoreStatus_Success)
552 {
553 storage.Remove(fileUuid);
554 storage.Remove(jsonUuid);
555 }
556
557 switch (status)
558 {
559 case StoreStatus_Success:
560 std::cout << "New instance stored: " << GetTotalSize() << std::endl;
561 break;
562
563 case StoreStatus_AlreadyStored:
564 std::cout << "Already stored" << std::endl;
565 break;
566
567 case StoreStatus_Failure:
568 std::cout << "Store failure" << std::endl;
569 break;
570 }
571
572 return status;
573 }
574
575 uint64_t ServerIndex::GetTotalSize()
576 {
577 boost::mutex::scoped_lock scoped_lock(mutex_);
578 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(fileSize) FROM Instances");
579 s.Run();
580 return s.ColumnInt64(0);
581 }
582
583
584 SeriesStatus ServerIndex::GetSeriesStatus(const std::string& seriesUuid)
585 {
586 int numberOfSlices;
587 if (!GetMainDicomIntTag(numberOfSlices, seriesUuid, DicomTag::NUMBER_OF_SLICES) ||
588 numberOfSlices < 0)
589 {
590 return SeriesStatus_Unknown;
591 }
592
593 // Loop over the instances of the series
594 //std::set<
595
596 // TODO
597 return SeriesStatus_Unknown;
598 }
599
600
601
602
603
604 bool ServerIndex::GetInstance(Json::Value& result,
605 const std::string& instanceUuid)
606 {
607 assert(result.type() == Json::objectValue);
608 boost::mutex::scoped_lock scoped_lock(mutex_);
609
610 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT parentSeries, dicomInstance, fileSize, fileUuid FROM Instances WHERE uuid=?");
611 s.BindString(0, instanceUuid);
612 if (!s.Step())
613 {
614 return false;
615 }
616 else
617 {
618 result["ID"] = instanceUuid;
619 result["ParentSeries"] = s.ColumnString(0);
620 result["DicomSOPInstanceUID"] = s.ColumnString(1);
621 result["FileSize"] = s.ColumnInt(2); // TODO switch to 64bit with JsonCpp 0.6?
622 result["FileUuid"] = s.ColumnString(3);
623 MainDicomTagsToJson(result, instanceUuid);
624 return true;
625 }
626 }
627
628
629 bool ServerIndex::GetSeries(Json::Value& result,
630 const std::string& seriesUuid)
631 {
632 assert(result.type() == Json::objectValue);
633 boost::mutex::scoped_lock scoped_lock(mutex_);
634
635 SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentStudy, dicomSeries FROM Series WHERE uuid=?");
636 s1.BindString(0, seriesUuid);
637 if (!s1.Step())
638 {
639 return false;
640 }
641
642 result["ID"] = seriesUuid;
643 result["ParentStudy"] = s1.ColumnString(0);
644 result["DicomSeriesInstanceUID"] = s1.ColumnString(1);
645 MainDicomTagsToJson(result, seriesUuid);
646
647 Json::Value instances(Json::arrayValue);
648 SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Instances WHERE parentSeries=?");
649 s2.BindString(0, seriesUuid);
650 while (s2.Step())
651 {
652 instances.append(s2.ColumnString(0));
653 }
654
655 result["Instances"] = instances;
656
657 return true;
658 }
659
660
661 bool ServerIndex::GetStudy(Json::Value& result,
662 const std::string& studyUuid)
663 {
664 assert(result.type() == Json::objectValue);
665 boost::mutex::scoped_lock scoped_lock(mutex_);
666
667 SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentPatient, dicomStudy FROM Studies WHERE uuid=?");
668 s1.BindString(0, studyUuid);
669 if (!s1.Step())
670 {
671 return false;
672 }
673
674 result["ID"] = studyUuid;
675 result["ParentPatient"] = s1.ColumnString(0);
676 result["DicomStudyInstanceUID"] = s1.ColumnString(1);
677 MainDicomTagsToJson(result, studyUuid);
678
679 Json::Value series(Json::arrayValue);
680 SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Series WHERE parentStudy=?");
681 s2.BindString(0, studyUuid);
682 while (s2.Step())
683 {
684 series.append(s2.ColumnString(0));
685 }
686
687 result["Series"] = series;
688 return true;
689 }
690
691
692 bool ServerIndex::GetPatient(Json::Value& result,
693 const std::string& patientUuid)
694 {
695 assert(result.type() == Json::objectValue);
696 boost::mutex::scoped_lock scoped_lock(mutex_);
697
698 SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT dicomPatientId FROM Patients WHERE uuid=?");
699 s1.BindString(0, patientUuid);
700 if (!s1.Step())
701 {
702 return false;
703 }
704
705 result["ID"] = patientUuid;
706 result["DicomPatientID"] = s1.ColumnString(0);
707 MainDicomTagsToJson(result, patientUuid);
708
709 Json::Value studies(Json::arrayValue);
710 SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Studies WHERE parentPatient=?");
711 s2.BindString(0, patientUuid);
712 while (s2.Step())
713 {
714 studies.append(s2.ColumnString(0));
715 }
716
717 result["Studies"] = studies;
718 return true;
719 }
720
721
722 bool ServerIndex::GetJsonFile(std::string& fileUuid,
723 const std::string& instanceUuid)
724 {
725 boost::mutex::scoped_lock scoped_lock(mutex_);
726
727 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT jsonUuid FROM Instances WHERE uuid=?");
728 s.BindString(0, instanceUuid);
729 if (s.Step())
730 {
731 fileUuid = s.ColumnString(0);
732 return true;
733 }
734 else
735 {
736 return false;
737 }
738 }
739
740 bool ServerIndex::GetDicomFile(std::string& fileUuid,
741 const std::string& instanceUuid)
742 {
743 boost::mutex::scoped_lock scoped_lock(mutex_);
744
745 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Instances WHERE uuid=?");
746 s.BindString(0, instanceUuid);
747 if (s.Step())
748 {
749 fileUuid = s.ColumnString(0);
750 return true;
751 }
752 else
753 {
754 return false;
755 }
756 }
757
758
759 void ServerIndex::GetAllUuids(Json::Value& target,
760 const std::string& tableName)
761 {
762 assert(target.type() == Json::arrayValue);
763 boost::mutex::scoped_lock scoped_lock(mutex_);
764
765 std::string query = "SELECT uuid FROM " + tableName;
766 SQLite::Statement s(db_, query);
767 while (s.Step())
768 {
769 target.append(s.ColumnString(0));
770 }
771 }
772
773
774 bool ServerIndex::GetChanges(Json::Value& target,
775 int64_t since,
776 const std::string& filter,
777 unsigned int maxResults)
778 {
779 assert(target.type() == Json::objectValue);
780 boost::mutex::scoped_lock scoped_lock(mutex_);
781
782 if (filter.size() != 0 &&
783 filter != "instances" &&
784 filter != "series" &&
785 filter != "studies" &&
786 filter != "patients")
787 {
788 return false;
789 }
790
791 std::auto_ptr<SQLite::Statement> s;
792 if (filter.size() == 0)
793 {
794 s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? "
795 "ORDER BY seq LIMIT ?"));
796 s->BindInt64(0, since);
797 s->BindInt(1, maxResults);
798 }
799 else
800 {
801 s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? "
802 "AND basePath=? ORDER BY seq LIMIT ?"));
803 s->BindInt64(0, since);
804 s->BindString(1, filter);
805 s->BindInt(2, maxResults);
806 }
807
808 int64_t lastSeq = 0;
809 Json::Value results(Json::arrayValue);
810 while (s->Step())
811 {
812 int64_t seq = s->ColumnInt64(0);
813 std::string basePath = s->ColumnString(1);
814 std::string uuid = s->ColumnString(2);
815
816 if (filter.size() == 0 ||
817 filter == basePath)
818 {
819 Json::Value change(Json::objectValue);
820 change["Seq"] = static_cast<int>(seq); // TODO JsonCpp in 64bit
821 change["BasePath"] = basePath;
822 change["ID"] = uuid;
823 results.append(change);
824 }
825
826 if (seq > lastSeq)
827 {
828 lastSeq = seq;
829 }
830 }
831
832 target["Results"] = results;
833 target["LastSeq"] = static_cast<int>(lastSeq); // TODO JsonCpp in 64bit
834
835 return true;
836 }
837 }