comparison PalanthirServer/ServerIndex.cpp @ 45:33d67e1ab173

r
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 05 Sep 2012 13:24:59 +0200
parents PalantirServer/ServerIndex.cpp@a08b085190e1
children a15e90e5d6fc
comparison
equal deleted inserted replaced
43:9be852ad33d2 45:33d67e1ab173
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 #ifndef NOMINMAX
26 #define NOMINMAX
27 #endif
28
29 #include "EmbeddedResources.h"
30 #include "../Core/Toolbox.h"
31 #include "../Core/Uuid.h"
32 #include "../Core/DicomFormat/DicomArray.h"
33 #include "../Core/SQLite/Transaction.h"
34 #include "FromDcmtkBridge.h"
35
36 #include <boost/lexical_cast.hpp>
37 #include <stdio.h>
38
39 namespace Palantir
40 {
41 namespace Internals
42 {
43 class DeleteFromFileStorageFunction : public SQLite::IScalarFunction
44 {
45 private:
46 FileStorage fileStorage_;
47
48 public:
49 DeleteFromFileStorageFunction(const std::string& path) :
50 fileStorage_(path)
51 {
52 }
53
54 virtual const char* GetName() const
55 {
56 return "DeleteFromFileStorage";
57 }
58
59 virtual unsigned int GetCardinality() const
60 {
61 return 1;
62 }
63
64 virtual void Compute(SQLite::FunctionContext& context)
65 {
66 std::string fileUuid = context.GetStringValue(0);
67 printf("Removing file [%s]\n", fileUuid.c_str());
68 if (Toolbox::IsUuid(fileUuid))
69 {
70 fileStorage_.Remove(fileUuid);
71 }
72 }
73 };
74
75
76 class SignalDeletedLevelFunction : public SQLite::IScalarFunction
77 {
78 private:
79 int remainingLevel_;
80 std::string remainingLevelUuid_;
81
82 public:
83 void Clear()
84 {
85 remainingLevel_ = std::numeric_limits<int>::max();
86 }
87
88 bool HasRemainingLevel() const
89 {
90 return (remainingLevel_ != 0 &&
91 remainingLevel_ != std::numeric_limits<int>::max());
92 }
93
94 const std::string& GetRemainingLevelUuid() const
95 {
96 assert(HasRemainingLevel());
97 return remainingLevelUuid_;
98 }
99
100 const char* GetRemainingLevelType() const
101 {
102 assert(HasRemainingLevel());
103 switch (remainingLevel_)
104 {
105 case 1:
106 return "patient";
107 case 2:
108 return "study";
109 case 3:
110 return "series";
111 default:
112 throw PalantirException(ErrorCode_InternalError);
113 }
114 }
115
116 virtual const char* GetName() const
117 {
118 return "SignalDeletedLevel";
119 }
120
121 virtual unsigned int GetCardinality() const
122 {
123 return 2;
124 }
125
126 virtual void Compute(SQLite::FunctionContext& context)
127 {
128 int level = context.GetIntValue(0);
129 if (level < remainingLevel_)
130 {
131 remainingLevel_ = level;
132 remainingLevelUuid_ = context.GetStringValue(1);
133 }
134
135 //printf("deleted level [%d] [%s]\n", level, context.GetStringValue(1).c_str());
136 }
137 };
138 }
139
140
141 void ServerIndex::StoreMainDicomTags(const std::string& uuid,
142 const DicomMap& map)
143 {
144 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
145
146 DicomArray flattened(map);
147 for (size_t i = 0; i < flattened.GetSize(); i++)
148 {
149 s.Reset();
150 s.BindString(0, uuid);
151 s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup());
152 s.BindInt(2, flattened.GetElement(i).GetTag().GetElement());
153 s.BindString(3, flattened.GetElement(i).GetValue().AsString());
154 s.Run();
155 }
156 }
157
158 bool ServerIndex::GetMainDicomStringTag(std::string& result,
159 const std::string& uuid,
160 const DicomTag& tag)
161 {
162 SQLite::Statement s(db_, SQLITE_FROM_HERE,
163 "SELECT * FROM MainDicomTags WHERE uuid=? AND tagGroup=? AND tagElement=?");
164 s.BindString(0, uuid);
165 s.BindInt(1, tag.GetGroup());
166 s.BindInt(2, tag.GetElement());
167 if (!s.Step())
168 {
169 return false;
170 }
171
172 result = s.ColumnString(0);
173 return true;
174 }
175
176 bool ServerIndex::GetMainDicomIntTag(int& result,
177 const std::string& uuid,
178 const DicomTag& tag)
179 {
180 std::string s;
181 if (!GetMainDicomStringTag(s, uuid, tag))
182 {
183 return false;
184 }
185
186 try
187 {
188 result = boost::lexical_cast<int>(s);
189 return true;
190 }
191 catch (boost::bad_lexical_cast)
192 {
193 return false;
194 }
195 }
196
197 bool ServerIndex::HasInstance(std::string& instanceUuid,
198 const std::string& dicomInstance)
199 {
200 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Instances WHERE dicomInstance=?");
201 s.BindString(0, dicomInstance);
202 if (s.Step())
203 {
204 instanceUuid = s.ColumnString(0);
205 return true;
206 }
207 else
208 {
209 return false;
210 }
211 }
212
213
214 void ServerIndex::RecordChange(const std::string& resourceType,
215 const std::string& uuid)
216 {
217 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?)");
218 s.BindString(0, resourceType);
219 s.BindString(1, uuid);
220 s.Run();
221 }
222
223
224 std::string ServerIndex::CreateInstance(const std::string& parentSeriesUuid,
225 const std::string& dicomInstance,
226 const DicomMap& dicomSummary,
227 const std::string& fileUuid,
228 uint64_t fileSize,
229 const std::string& jsonUuid,
230 const std::string& distantAet)
231 {
232 std::string instanceUuid = Toolbox::GenerateUuid();
233
234 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Instances VALUES(?, ?, ?, ?, ?, ?, ?)");
235 s.BindString(0, instanceUuid);
236 s.BindString(1, parentSeriesUuid);
237 s.BindString(2, dicomInstance);
238 s.BindString(3, fileUuid);
239 s.BindInt64(4, fileSize);
240 s.BindString(5, jsonUuid);
241 s.BindString(6, distantAet);
242 s.Run();
243
244 RecordChange("instances", instanceUuid);
245
246 DicomMap dicom;
247 dicomSummary.ExtractInstanceInformation(dicom);
248 StoreMainDicomTags(instanceUuid, dicom);
249
250 return instanceUuid;
251 }
252
253 void ServerIndex::RemoveInstance(const std::string& uuid)
254 {
255 SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Instances WHERE uuid=?");
256 s.BindString(0, uuid);
257 s.Run();
258 }
259
260 bool ServerIndex::HasSeries(std::string& seriesUuid,
261 const std::string& dicomSeries)
262 {
263 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Series WHERE dicomSeries=?");
264 s.BindString(0, dicomSeries);
265 if (s.Step())
266 {
267 seriesUuid = s.ColumnString(0);
268 return true;
269 }
270 else
271 {
272 return false;
273 }
274 }
275
276 std::string ServerIndex::CreateSeries(const std::string& parentStudyUuid,
277 const std::string& dicomSeries,
278 const DicomMap& dicomSummary)
279 {
280 std::string seriesUuid = Toolbox::GenerateUuid();
281
282 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Series VALUES(?, ?, ?)");
283 s.BindString(0, seriesUuid);
284 s.BindString(1, parentStudyUuid);
285 s.BindString(2, dicomSeries);
286 s.Run();
287
288 RecordChange("series", seriesUuid);
289
290 DicomMap dicom;
291 dicomSummary.ExtractSeriesInformation(dicom);
292 StoreMainDicomTags(seriesUuid, dicom);
293
294 return seriesUuid;
295 }
296
297 bool ServerIndex::HasStudy(std::string& studyUuid,
298 const std::string& dicomStudy)
299 {
300 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Studies WHERE dicomStudy=?");
301 s.BindString(0, dicomStudy);
302 if (s.Step())
303 {
304 studyUuid = s.ColumnString(0);
305 return true;
306 }
307 else
308 {
309 return false;
310 }
311 }
312
313 std::string ServerIndex::CreateStudy(const std::string& parentPatientUuid,
314 const std::string& dicomStudy,
315 const DicomMap& dicomSummary)
316 {
317 std::string studyUuid = Toolbox::GenerateUuid();
318
319 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Studies VALUES(?, ?, ?)");
320 s.BindString(0, studyUuid);
321 s.BindString(1, parentPatientUuid);
322 s.BindString(2, dicomStudy);
323 s.Run();
324
325 RecordChange("studies", studyUuid);
326
327 DicomMap dicom;
328 dicomSummary.ExtractStudyInformation(dicom);
329 StoreMainDicomTags(studyUuid, dicom);
330
331 return studyUuid;
332 }
333
334
335
336 bool ServerIndex::HasPatient(std::string& patientUuid,
337 const std::string& dicomPatientId)
338 {
339 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Patients WHERE dicomPatientId=?");
340 s.BindString(0, dicomPatientId);
341 if (s.Step())
342 {
343 patientUuid = s.ColumnString(0);
344 return true;
345 }
346 else
347 {
348 return false;
349 }
350 }
351
352 std::string ServerIndex::CreatePatient(const std::string& patientId,
353 const DicomMap& dicomSummary)
354 {
355 std::string patientUuid = Toolbox::GenerateUuid();
356 std::string dicomPatientId = dicomSummary.GetValue(DicomTag::PATIENT_ID).AsString();
357
358 SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Patients VALUES(?, ?)");
359 s.BindString(0, patientUuid);
360 s.BindString(1, dicomPatientId);
361 s.Run();
362
363 RecordChange("patients", patientUuid);
364
365 DicomMap dicom;
366 dicomSummary.ExtractPatientInformation(dicom);
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["FileSize"] = s.ColumnInt(2); // TODO switch to 64bit with JsonCpp 0.6?
621 result["FileUuid"] = s.ColumnString(3);
622 MainDicomTagsToJson(result, instanceUuid);
623 return true;
624 }
625 }
626
627
628 bool ServerIndex::GetSeries(Json::Value& result,
629 const std::string& seriesUuid)
630 {
631 assert(result.type() == Json::objectValue);
632 boost::mutex::scoped_lock scoped_lock(mutex_);
633
634 SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentStudy, dicomSeries FROM Series WHERE uuid=?");
635 s1.BindString(0, seriesUuid);
636 if (!s1.Step())
637 {
638 return false;
639 }
640
641 result["ID"] = seriesUuid;
642 result["ParentStudy"] = s1.ColumnString(0);
643 MainDicomTagsToJson(result, seriesUuid);
644
645 Json::Value instances(Json::arrayValue);
646 SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Instances WHERE parentSeries=?");
647 s2.BindString(0, seriesUuid);
648 while (s2.Step())
649 {
650 instances.append(s2.ColumnString(0));
651 }
652
653 result["Instances"] = instances;
654
655 return true;
656 }
657
658
659 bool ServerIndex::GetStudy(Json::Value& result,
660 const std::string& studyUuid)
661 {
662 assert(result.type() == Json::objectValue);
663 boost::mutex::scoped_lock scoped_lock(mutex_);
664
665 SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT parentPatient, dicomStudy FROM Studies WHERE uuid=?");
666 s1.BindString(0, studyUuid);
667 if (!s1.Step())
668 {
669 return false;
670 }
671
672 result["ID"] = studyUuid;
673 result["ParentPatient"] = s1.ColumnString(0);
674 MainDicomTagsToJson(result, studyUuid);
675
676 Json::Value series(Json::arrayValue);
677 SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Series WHERE parentStudy=?");
678 s2.BindString(0, studyUuid);
679 while (s2.Step())
680 {
681 series.append(s2.ColumnString(0));
682 }
683
684 result["Series"] = series;
685 return true;
686 }
687
688
689 bool ServerIndex::GetPatient(Json::Value& result,
690 const std::string& patientUuid)
691 {
692 assert(result.type() == Json::objectValue);
693 boost::mutex::scoped_lock scoped_lock(mutex_);
694
695 SQLite::Statement s1(db_, SQLITE_FROM_HERE, "SELECT dicomPatientId FROM Patients WHERE uuid=?");
696 s1.BindString(0, patientUuid);
697 if (!s1.Step())
698 {
699 return false;
700 }
701
702 result["ID"] = patientUuid;
703 MainDicomTagsToJson(result, patientUuid);
704
705 Json::Value studies(Json::arrayValue);
706 SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT uuid FROM Studies WHERE parentPatient=?");
707 s2.BindString(0, patientUuid);
708 while (s2.Step())
709 {
710 studies.append(s2.ColumnString(0));
711 }
712
713 result["Studies"] = studies;
714 return true;
715 }
716
717
718 bool ServerIndex::GetJsonFile(std::string& fileUuid,
719 const std::string& instanceUuid)
720 {
721 boost::mutex::scoped_lock scoped_lock(mutex_);
722
723 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT jsonUuid FROM Instances WHERE uuid=?");
724 s.BindString(0, instanceUuid);
725 if (s.Step())
726 {
727 fileUuid = s.ColumnString(0);
728 return true;
729 }
730 else
731 {
732 return false;
733 }
734 }
735
736 bool ServerIndex::GetDicomFile(std::string& fileUuid,
737 const std::string& instanceUuid)
738 {
739 boost::mutex::scoped_lock scoped_lock(mutex_);
740
741 SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Instances WHERE uuid=?");
742 s.BindString(0, instanceUuid);
743 if (s.Step())
744 {
745 fileUuid = s.ColumnString(0);
746 return true;
747 }
748 else
749 {
750 return false;
751 }
752 }
753
754
755 void ServerIndex::GetAllUuids(Json::Value& target,
756 const std::string& tableName)
757 {
758 assert(target.type() == Json::arrayValue);
759 boost::mutex::scoped_lock scoped_lock(mutex_);
760
761 std::string query = "SELECT uuid FROM " + tableName;
762 SQLite::Statement s(db_, query);
763 while (s.Step())
764 {
765 target.append(s.ColumnString(0));
766 }
767 }
768
769
770 bool ServerIndex::GetChanges(Json::Value& target,
771 int64_t since,
772 const std::string& filter,
773 unsigned int maxResults)
774 {
775 assert(target.type() == Json::objectValue);
776 boost::mutex::scoped_lock scoped_lock(mutex_);
777
778 if (filter.size() != 0 &&
779 filter != "instances" &&
780 filter != "series" &&
781 filter != "studies" &&
782 filter != "patients")
783 {
784 return false;
785 }
786
787 std::auto_ptr<SQLite::Statement> s;
788 if (filter.size() == 0)
789 {
790 s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? "
791 "ORDER BY seq LIMIT ?"));
792 s->BindInt64(0, since);
793 s->BindInt(1, maxResults);
794 }
795 else
796 {
797 s.reset(new SQLite::Statement(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? "
798 "AND basePath=? ORDER BY seq LIMIT ?"));
799 s->BindInt64(0, since);
800 s->BindString(1, filter);
801 s->BindInt(2, maxResults);
802 }
803
804 int64_t lastSeq = 0;
805 Json::Value results(Json::arrayValue);
806 while (s->Step())
807 {
808 int64_t seq = s->ColumnInt64(0);
809 std::string basePath = s->ColumnString(1);
810 std::string uuid = s->ColumnString(2);
811
812 if (filter.size() == 0 ||
813 filter == basePath)
814 {
815 Json::Value change(Json::objectValue);
816 change["Seq"] = static_cast<int>(seq); // TODO JsonCpp in 64bit
817 change["BasePath"] = basePath;
818 change["ID"] = uuid;
819 results.append(change);
820 }
821
822 if (seq > lastSeq)
823 {
824 lastSeq = seq;
825 }
826 }
827
828 target["Results"] = results;
829 target["LastSeq"] = static_cast<int>(lastSeq); // TODO JsonCpp in 64bit
830
831 return true;
832 }
833 }