comparison OrthancServer/Sources/main.cpp @ 4234:a38376b80cd1

WebDAV: by-studies and by-patients
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 08 Oct 2020 13:38:44 +0200
parents ca2a55a62c81
children b3ec19f369d1
comparison
equal deleted inserted replaced
4233:ca2a55a62c81 4234:a38376b80cd1
57 #include "ServerToolbox.h" 57 #include "ServerToolbox.h"
58 #include "StorageCommitmentReports.h" 58 #include "StorageCommitmentReports.h"
59 59
60 #include "../../OrthancFramework/Sources/HttpServer/WebDavStorage.h" // TODO 60 #include "../../OrthancFramework/Sources/HttpServer/WebDavStorage.h" // TODO
61 #include "Search/DatabaseLookup.h" // TODO 61 #include "Search/DatabaseLookup.h" // TODO
62 #include <boost/regex.hpp> // TODO
62 63
63 64
64 using namespace Orthanc; 65 using namespace Orthanc;
65 66
66 67
689 } 690 }
690 } 691 }
691 692
692 virtual bool GetFileContent(MimeType& mime, 693 virtual bool GetFileContent(MimeType& mime,
693 std::string& content, 694 std::string& content,
694 boost::posix_time::ptime& modificationTime, 695 boost::posix_time::ptime& time,
695 const UriComponents& path) ORTHANC_OVERRIDE 696 const UriComponents& path) ORTHANC_OVERRIDE
696 { 697 {
697 if (path.empty()) 698 if (path.empty())
698 { 699 {
699 return false; 700 return false;
700 } 701 }
701 else if (IsUploadedFolder(path)) 702 else if (IsUploadedFolder(path))
702 { 703 {
703 return storage_.GetFileContent(mime, content, modificationTime, 704 return storage_.GetFileContent(mime, content, time,
704 UriComponents(path.begin() + 1, path.end())); 705 UriComponents(path.begin() + 1, path.end()));
705 } 706 }
706 else if (path.back() == "IM0.dcm" || 707 else if (path.back() == "IM0.dcm" ||
707 path.back() == "IM1.dcm" || 708 path.back() == "IM1.dcm" ||
708 path.back() == "IM2.dcm" || 709 path.back() == "IM2.dcm" ||
709 path.back() == "IM3.dcm" || 710 path.back() == "IM3.dcm" ||
710 path.back() == "IM4.dcm") 711 path.back() == "IM4.dcm")
711 { 712 {
712 modificationTime = boost::posix_time::second_clock::universal_time(); 713 time = boost::posix_time::second_clock::universal_time();
713 714
714 std::string s; 715 std::string s;
715 for (size_t i = 0; i < path.size(); i++) 716 for (size_t i = 0; i < path.size(); i++)
716 { 717 {
717 s += "/" + path[i]; 718 s += "/" + path[i];
774 775
775 776
776 777
777 778
778 779
780 static const char* const BY_PATIENTS = "by-patients";
781 static const char* const BY_STUDIES = "by-studies";
779 static const char* const BY_UIDS = "by-uids"; 782 static const char* const BY_UIDS = "by-uids";
783 static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
780 784
781 class DummyBucket2 : public IWebDavBucket // TODO 785 class DummyBucket2 : public IWebDavBucket // TODO
782 { 786 {
783 private: 787 private:
784 ServerContext& context_; 788 ServerContext& context_;
839 const std::string& instanceId /* unused */, 843 const std::string& instanceId /* unused */,
840 const DicomMap& mainDicomTags, 844 const DicomMap& mainDicomTags,
841 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE 845 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE
842 { 846 {
843 DicomTag tag(0, 0); 847 DicomTag tag(0, 0);
844 MetadataType dateMetadata; 848 MetadataType timeMetadata;
845 849
846 switch (level_) 850 switch (level_)
847 { 851 {
848 case ResourceType_Study: 852 case ResourceType_Study:
849 tag = DICOM_TAG_STUDY_INSTANCE_UID; 853 tag = DICOM_TAG_STUDY_INSTANCE_UID;
850 dateMetadata = MetadataType_LastUpdate; 854 timeMetadata = MetadataType_LastUpdate;
851 break; 855 break;
852 856
853 case ResourceType_Series: 857 case ResourceType_Series:
854 tag = DICOM_TAG_SERIES_INSTANCE_UID; 858 tag = DICOM_TAG_SERIES_INSTANCE_UID;
855 dateMetadata = MetadataType_LastUpdate; 859 timeMetadata = MetadataType_LastUpdate;
856 break; 860 break;
857 861
858 case ResourceType_Instance: 862 case ResourceType_Instance:
859 tag = DICOM_TAG_SOP_INSTANCE_UID; 863 tag = DICOM_TAG_SOP_INSTANCE_UID;
860 dateMetadata = MetadataType_Instance_ReceptionDate; 864 timeMetadata = MetadataType_Instance_ReceptionDate;
861 break; 865 break;
862 866
863 default: 867 default:
864 throw OrthancException(ErrorCode_InternalError); 868 throw OrthancException(ErrorCode_InternalError);
865 } 869 }
884 else 888 else
885 { 889 {
886 resource.reset(new Folder(s)); 890 resource.reset(new Folder(s));
887 } 891 }
888 892
889 boost::posix_time::ptime t; 893 if (resource.get() != NULL)
890 LookupTime(t, context_, publicId, dateMetadata); 894 {
891 resource->SetCreationTime(t); 895 boost::posix_time::ptime t;
892 896 LookupTime(t, context_, publicId, timeMetadata);
893 target_.AddResource(resource.release()); 897 resource->SetCreationTime(t);
898 target_.AddResource(resource.release());
899 }
894 } 900 }
895 } 901 }
896 }; 902 };
897 903
898 class DicomFileVisitor : public ServerContext::ILookupVisitor 904 class DicomFileVisitor : public ServerContext::ILookupVisitor
899 { 905 {
900 private: 906 private:
901 ServerContext& context_; 907 ServerContext& context_;
902 bool success_; 908 bool success_;
903 std::string& target_; 909 std::string& target_;
904 boost::posix_time::ptime& modificationTime_; 910 boost::posix_time::ptime& time_;
905 911
906 public: 912 public:
907 DicomFileVisitor(ServerContext& context, 913 DicomFileVisitor(ServerContext& context,
908 std::string& target, 914 std::string& target,
909 boost::posix_time::ptime& modificationTime) : 915 boost::posix_time::ptime& time) :
910 context_(context), 916 context_(context),
911 success_(false), 917 success_(false),
912 target_(target), 918 target_(target),
913 modificationTime_(modificationTime) 919 time_(time)
914 { 920 {
915 } 921 }
916 922
917 bool IsSuccess() const 923 bool IsSuccess() const
918 { 924 {
937 { 943 {
938 success_ = false; // Two matches => Error 944 success_ = false; // Two matches => Error
939 } 945 }
940 else 946 else
941 { 947 {
942 LookupTime(modificationTime_, context_, publicId, MetadataType_Instance_ReceptionDate); 948 LookupTime(time_, context_, publicId, MetadataType_Instance_ReceptionDate);
943 context_.ReadDicom(target_, publicId); 949 context_.ReadDicom(target_, publicId);
944 success_ = true; 950 success_ = true;
945 } 951 }
946 } 952 }
947 }; 953 };
1022 f->SetMimeType(mime); 1028 f->SetMimeType(mime);
1023 f->SetContentLength(content.size()); 1029 f->SetContentLength(content.size());
1024 f->SetCreationTime(modification); 1030 f->SetCreationTime(modification);
1025 collection.AddResource(f.release()); 1031 collection.AddResource(f.release());
1026 } 1032 }
1027 } 1033 }
1034
1035
1036
1037
1038 class ResourcesIndex : public boost::noncopyable
1039 {
1040 public:
1041 typedef std::map<std::string, std::string> Map;
1042
1043 private:
1044 ServerContext& context_;
1045 ResourceType level_;
1046 std::string template_;
1047 Map pathToResource_;
1048 Map resourceToPath_;
1049
1050 void CheckInvariants()
1051 {
1052 #ifndef NDEBUG
1053 assert(pathToResource_.size() == resourceToPath_.size());
1054
1055 for (Map::const_iterator it = pathToResource_.begin(); it != pathToResource_.end(); ++it)
1056 {
1057 assert(resourceToPath_[it->second] == it->first);
1058 }
1059
1060 for (Map::const_iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it)
1061 {
1062 assert(pathToResource_[it->second] == it->first);
1063 }
1064 #endif
1065 }
1066
1067 void AddTags(DicomMap& target,
1068 const std::string& resourceId,
1069 ResourceType tagsFromLevel)
1070 {
1071 DicomMap tags;
1072 if (context_.GetIndex().GetMainDicomTags(tags, resourceId, level_, tagsFromLevel))
1073 {
1074 target.Merge(tags);
1075 }
1076 }
1077
1078 void Register(const std::string& resourceId)
1079 {
1080 // Don't register twice the same resource
1081 if (resourceToPath_.find(resourceId) == resourceToPath_.end())
1082 {
1083 std::string name = template_;
1084
1085 DicomMap tags;
1086
1087 AddTags(tags, resourceId, level_);
1088
1089 if (level_ == ResourceType_Study)
1090 {
1091 AddTags(tags, resourceId, ResourceType_Patient);
1092 }
1093
1094 DicomArray arr(tags);
1095 for (size_t i = 0; i < arr.GetSize(); i++)
1096 {
1097 const DicomElement& element = arr.GetElement(i);
1098 if (!element.GetValue().IsNull() &&
1099 !element.GetValue().IsBinary())
1100 {
1101 const std::string tag = FromDcmtkBridge::GetTagName(element.GetTag(), "");
1102 boost::replace_all(name, "{{" + tag + "}}", element.GetValue().GetContent());
1103 }
1104 }
1105
1106 // Blank the tags that were not matched
1107 static const boost::regex REGEX_BLANK_TAGS("{{.*?}}"); // non-greedy match
1108 name = boost::regex_replace(name, REGEX_BLANK_TAGS, "");
1109
1110 // UTF-8 characters cannot be used on Windows XP
1111 name = Toolbox::ConvertToAscii(name);
1112 boost::replace_all(name, "/", "");
1113 boost::replace_all(name, "\\", "");
1114
1115 // Trim sequences of spaces as one single space
1116 static const boost::regex REGEX_TRIM_SPACES("{{.*?}}");
1117 name = boost::regex_replace(name, REGEX_TRIM_SPACES, " ");
1118 name = Toolbox::StripSpaces(name);
1119
1120 size_t count = 0;
1121 for (;;)
1122 {
1123 std::string path = name;
1124 if (count > 0)
1125 {
1126 path += " (" + boost::lexical_cast<std::string>(count) + ")";
1127 }
1128
1129 if (pathToResource_.find(path) == pathToResource_.end())
1130 {
1131 pathToResource_[path] = resourceId;
1132 resourceToPath_[resourceId] = path;
1133 return;
1134 }
1135
1136 count++;
1137 }
1138
1139 throw OrthancException(ErrorCode_InternalError);
1140 }
1141 }
1142
1143 public:
1144 ResourcesIndex(ServerContext& context,
1145 ResourceType level,
1146 const std::string& templateString) :
1147 context_(context),
1148 level_(level),
1149 template_(templateString)
1150 {
1151 }
1152
1153 ResourceType GetLevel() const
1154 {
1155 return level_;
1156 }
1157
1158 void Refresh(std::set<std::string>& removedPaths /* out */,
1159 const std::set<std::string>& resources)
1160 {
1161 CheckInvariants();
1162
1163 // Detect the resources that have been removed since last refresh
1164 removedPaths.clear();
1165 std::set<std::string> removedResources;
1166
1167 for (Map::iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it)
1168 {
1169 if (resources.find(it->first) == resources.end())
1170 {
1171 const std::string& path = it->second;
1172
1173 assert(pathToResource_.find(path) != pathToResource_.end());
1174 pathToResource_.erase(path);
1175 removedPaths.insert(path);
1176
1177 removedResources.insert(it->first); // Delay the removal to avoid disturbing the iterator
1178 }
1179 }
1180
1181 // Remove the missing resources
1182 for (std::set<std::string>::const_iterator it = removedResources.begin(); it != removedResources.end(); ++it)
1183 {
1184 assert(resourceToPath_.find(*it) != resourceToPath_.end());
1185 resourceToPath_.erase(*it);
1186 }
1187
1188 CheckInvariants();
1189
1190 for (std::set<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it)
1191 {
1192 Register(*it);
1193 }
1194
1195 CheckInvariants();
1196 }
1197
1198 const Map& GetPathToResource() const
1199 {
1200 return pathToResource_;
1201 }
1202 };
1203
1204
1205 class INode : public boost::noncopyable
1206 {
1207 public:
1208 virtual ~INode()
1209 {
1210 }
1211
1212 virtual bool ListCollection(IWebDavBucket::Collection& target,
1213 const UriComponents& path) = 0;
1214
1215 virtual bool GetFileContent(MimeType& mime,
1216 std::string& content,
1217 boost::posix_time::ptime& time,
1218 const UriComponents& path) = 0;
1219 };
1220
1221
1222 class InstancesNode : public INode
1223 {
1224 private:
1225 ServerContext& context_;
1226 std::string parentSeries_;
1227
1228 public:
1229 InstancesNode(ServerContext& context,
1230 const std::string& parentSeries) :
1231 context_(context),
1232 parentSeries_(parentSeries)
1233 {
1234 }
1235
1236 virtual bool ListCollection(IWebDavBucket::Collection& target,
1237 const UriComponents& path) ORTHANC_OVERRIDE
1238 {
1239 if (path.empty())
1240 {
1241 std::list<std::string> resources;
1242 try
1243 {
1244 context_.GetIndex().GetChildren(resources, parentSeries_);
1245 }
1246 catch (OrthancException&)
1247 {
1248 // Unknown (or deleted) parent series
1249 return false;
1250 }
1251
1252 for (std::list<std::string>::const_iterator
1253 it = resources.begin(); it != resources.end(); ++it)
1254 {
1255 boost::posix_time::ptime time;
1256 LookupTime(time, context_, *it, MetadataType_Instance_ReceptionDate);
1257
1258 FileInfo info;
1259 if (context_.GetIndex().LookupAttachment(info, *it, FileContentType_Dicom))
1260 {
1261 std::unique_ptr<File> resource(new File(*it + ".dcm"));
1262 resource->SetMimeType(MimeType_Dicom);
1263 resource->SetContentLength(info.GetUncompressedSize());
1264 resource->SetCreationTime(time);
1265 target.AddResource(resource.release());
1266 }
1267 }
1268
1269 return true;
1270 }
1271 else
1272 {
1273 return false;
1274 }
1275 }
1276
1277 virtual bool GetFileContent(MimeType& mime,
1278 std::string& content,
1279 boost::posix_time::ptime& time,
1280 const UriComponents& path) ORTHANC_OVERRIDE
1281 {
1282 if (path.size() == 1 &&
1283 boost::ends_with(path[0], ".dcm"))
1284 {
1285 std::string instanceId = path[0].substr(0, path[0].size() - 4);
1286
1287 try
1288 {
1289 mime = MimeType_Dicom;
1290 context_.ReadDicom(content, instanceId);
1291 LookupTime(time, context_, instanceId, MetadataType_Instance_ReceptionDate);
1292 return true;
1293 }
1294 catch (OrthancException&)
1295 {
1296 // File was removed
1297 return false;
1298 }
1299 }
1300 else
1301 {
1302 return false;
1303 }
1304 }
1305 };
1306
1307
1308
1309 class ResourcesNode : public INode
1310 {
1311 private:
1312 typedef std::map<std::string, INode*> Children;
1313
1314 ServerContext& context_;
1315 ResourcesIndex index_;
1316 MetadataType timeMetadata_;
1317 Children children_; // Maps Orthanc resource IDs to subnodes
1318
1319 void Refresh()
1320 {
1321 std::list<std::string> resources;
1322 GetCurrentResources(resources);
1323
1324 std::set<std::string> removedPaths;
1325 index_.Refresh(removedPaths, std::set<std::string>(resources.begin(), resources.end()));
1326
1327 // Remove the children that have been removed
1328 for (std::set<std::string>::const_iterator
1329 it = removedPaths.begin(); it != removedPaths.end(); ++it)
1330 {
1331 Children::iterator child = children_.find(*it);
1332 if (child != children_.end())
1333 {
1334 assert(child->second != NULL);
1335 delete child->second;
1336 children_.erase(child);
1337 }
1338 }
1339 }
1340
1341 INode* GetChild(const std::string& path) // Don't free the resulting pointer!
1342 {
1343 ResourcesIndex::Map::const_iterator resource = index_.GetPathToResource().find(path);
1344 if (resource == index_.GetPathToResource().end())
1345 {
1346 return NULL;
1347 }
1348 else
1349 {
1350 Children::iterator child = children_.find(resource->second);
1351 if (child != children_.end())
1352 {
1353 assert(child->second != NULL);
1354 return child->second;
1355 }
1356 else
1357 {
1358 INode* child = CreateChild(resource->second);
1359 if (child == NULL)
1360 {
1361 return NULL;
1362 }
1363 else
1364 {
1365 children_[resource->second] = child;
1366 return child;
1367 }
1368 }
1369 }
1370 }
1371
1372 protected:
1373 ServerContext& GetContext() const
1374 {
1375 return context_;
1376 }
1377
1378 virtual void GetCurrentResources(std::list<std::string>& resources) = 0;
1379
1380 virtual INode* CreateChild(const std::string& resource) = 0;
1381
1382 public:
1383 ResourcesNode(ServerContext& context,
1384 ResourceType level,
1385 const std::string& templateString) :
1386 context_(context),
1387 index_(context, level, templateString)
1388 {
1389 if (level == ResourceType_Instance)
1390 {
1391 timeMetadata_ = MetadataType_Instance_ReceptionDate;
1392 }
1393 else
1394 {
1395 timeMetadata_ = MetadataType_LastUpdate;
1396 }
1397 }
1398
1399 virtual ~ResourcesNode()
1400 {
1401 for (Children::iterator it = children_.begin(); it != children_.end(); ++it)
1402 {
1403 assert(it->second != NULL);
1404 delete it->second;
1405 }
1406 }
1407
1408 ResourceType GetLevel() const
1409 {
1410 return index_.GetLevel();
1411 }
1412
1413 virtual bool ListCollection(IWebDavBucket::Collection& target,
1414 const UriComponents& path) ORTHANC_OVERRIDE
1415 {
1416 Refresh();
1417
1418 if (index_.GetLevel() == ResourceType_Instance)
1419 {
1420 // Not a collection, no subfolders
1421 return false;
1422 }
1423 else if (path.empty())
1424 {
1425 const ResourcesIndex::Map& paths = index_.GetPathToResource();
1426
1427 for (ResourcesIndex::Map::const_iterator it = paths.begin(); it != paths.end(); ++it)
1428 {
1429 boost::posix_time::ptime time;
1430 LookupTime(time, context_, it->second, timeMetadata_);
1431
1432 std::unique_ptr<IWebDavBucket::Resource> resource(new IWebDavBucket::Folder(it->first));
1433 resource->SetCreationTime(time);
1434 target.AddResource(resource.release());
1435 }
1436
1437 return true;
1438 }
1439 else
1440 {
1441 // Recursivity
1442 INode* child = GetChild(path[0]);
1443 if (child == NULL)
1444 {
1445 return false;
1446 }
1447 else
1448 {
1449 UriComponents subpath(path.begin() + 1, path.end());
1450 return child->ListCollection(target, subpath);
1451 }
1452 }
1453 }
1454
1455 virtual bool GetFileContent(MimeType& mime,
1456 std::string& content,
1457 boost::posix_time::ptime& time,
1458 const UriComponents& path) ORTHANC_OVERRIDE
1459 {
1460 Refresh();
1461
1462 if (path.empty())
1463 {
1464 return false;
1465 }
1466 else
1467 {
1468 // Recursivity
1469 INode* child = GetChild(path[0]);
1470 if (child == NULL)
1471 {
1472 return false;
1473 }
1474 else
1475 {
1476 UriComponents subpath(path.begin() + 1, path.end());
1477 return child->GetFileContent(mime, content, time, subpath);
1478 }
1479 }
1480 }
1481 };
1482
1028 1483
1484
1485 class ParentNode : public ResourcesNode
1486 {
1487 private:
1488 std::string parentId_;
1489
1490 protected:
1491 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
1492 {
1493 try
1494 {
1495 GetContext().GetIndex().GetChildren(resources, parentId_);
1496 }
1497 catch (OrthancException&)
1498 {
1499 // Unknown parent resource
1500 resources.clear();
1501 }
1502 }
1503
1504 virtual INode* CreateChild(const std::string& resource) ORTHANC_OVERRIDE
1505 {
1506 if (GetLevel() == ResourceType_Instance)
1507 {
1508 return NULL;
1509 }
1510 else if (GetLevel() == ResourceType_Series)
1511 {
1512 return new InstancesNode(GetContext(), resource);
1513 }
1514 else
1515 {
1516 std::string t;
1517
1518 ResourceType l = GetChildResourceType(GetLevel());
1519 switch (l)
1520 {
1521 case ResourceType_Study:
1522 t = "{{StudyDate}} - {{StudyDescription}}";
1523 break;
1524
1525 case ResourceType_Series:
1526 t = "{{Modality}} - {{SeriesDescription}}";
1527 break;
1528
1529 default:
1530 throw OrthancException(ErrorCode_InternalError);
1531 }
1532
1533 return new ParentNode(GetContext(), l, resource, t);
1534 }
1535 }
1536
1537 public:
1538 ParentNode(ServerContext& context,
1539 ResourceType level,
1540 const std::string& parentId,
1541 const std::string& templateString) :
1542 ResourcesNode(context, level, templateString),
1543 parentId_(parentId)
1544 {
1545 }
1546 };
1547
1548
1549 class RootNode : public ResourcesNode
1550 {
1551 protected:
1552 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
1553 {
1554 GetContext().GetIndex().GetAllUuids(resources, GetLevel());
1555 }
1556
1557 virtual INode* CreateChild(const std::string& resource) ORTHANC_OVERRIDE
1558 {
1559 if (GetLevel() == ResourceType_Series)
1560 {
1561 return new InstancesNode(GetContext(), resource);
1562 }
1563 else
1564 {
1565 std::string t;
1566
1567 ResourceType l = GetChildResourceType(GetLevel());
1568 switch (l)
1569 {
1570 case ResourceType_Study:
1571 t = "{{StudyDate}} - {{StudyDescription}}";
1572 break;
1573
1574 case ResourceType_Series:
1575 t = "{{Modality}} - {{SeriesDescription}}";
1576 break;
1577
1578 default:
1579 throw OrthancException(ErrorCode_InternalError);
1580 }
1581
1582 printf("OPENING CHILDREN of %s %s\n", EnumerationToString(GetLevel()), resource.c_str());
1583
1584 return new ParentNode(GetContext(), l, resource, t);
1585 }
1586 }
1587
1588 public:
1589 RootNode(ServerContext& context,
1590 ResourceType level,
1591 const std::string& templateString) :
1592 ResourcesNode(context, level, templateString)
1593 {
1594 }
1595 };
1596
1597
1598
1599 RootNode patients_;
1600 RootNode studies_;
1601
1602
1029 public: 1603 public:
1030 DummyBucket2(ServerContext& context) : 1604 DummyBucket2(ServerContext& context) :
1031 context_(context) 1605 context_(context),
1032 { 1606 patients_(context, ResourceType_Patient, "{{PatientID}} - {{PatientName}}"),
1033 } 1607 studies_(context, ResourceType_Study, "{{PatientID}} - {{PatientName}} - {{StudyDescription}}")
1034 1608 {
1609 }
1610
1035 virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE 1611 virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE
1036 { 1612 {
1037 if (path.empty()) 1613 if (path.empty())
1038 { 1614 {
1039 return true; 1615 return true;
1040 } 1616 }
1041 else if (path.front() == BY_UIDS) 1617 else if (path[0] == BY_UIDS)
1042 { 1618 {
1043 return (path.size() <= 3 && 1619 return (path.size() <= 3 &&
1044 (path.size() != 3 || path[2] != "study.json")); 1620 (path.size() != 3 || path[2] != "study.json"));
1045 } 1621 }
1622 else if (path[0] == BY_PATIENTS)
1623 {
1624 IWebDavBucket::Collection tmp;
1625 return patients_.ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
1626 }
1627 else if (path[0] == BY_STUDIES)
1628 {
1629 IWebDavBucket::Collection tmp;
1630 return studies_.ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
1631 }
1046 else 1632 else
1047 { 1633 {
1048 return false; 1634 return false;
1049 } 1635 }
1050 } 1636 }
1053 const UriComponents& path) ORTHANC_OVERRIDE 1639 const UriComponents& path) ORTHANC_OVERRIDE
1054 { 1640 {
1055 if (path.empty()) 1641 if (path.empty())
1056 { 1642 {
1057 collection.AddResource(new Folder(BY_UIDS)); 1643 collection.AddResource(new Folder(BY_UIDS));
1644 collection.AddResource(new Folder(BY_PATIENTS));
1645 collection.AddResource(new Folder(BY_STUDIES));
1058 return true; 1646 return true;
1059 } 1647 }
1060 else if (path.front() == BY_UIDS) 1648 else if (path[0] == BY_UIDS)
1061 { 1649 {
1062 DatabaseLookup query; 1650 DatabaseLookup query;
1063 ResourceType level; 1651 ResourceType level;
1064 size_t limit = 0; // By default, no limits 1652 size_t limit = 0; // By default, no limits
1065 1653
1094 DicomIdentifiersVisitor visitor(context_, collection, level); 1682 DicomIdentifiersVisitor visitor(context_, collection, level);
1095 context_.Apply(visitor, query, level, 0 /* since */, limit); 1683 context_.Apply(visitor, query, level, 0 /* since */, limit);
1096 1684
1097 return true; 1685 return true;
1098 } 1686 }
1687 else if (path[0] == BY_PATIENTS)
1688 {
1689 return patients_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
1690 }
1691 else if (path[0] == BY_STUDIES)
1692 {
1693 return studies_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
1694 }
1099 else 1695 else
1100 { 1696 {
1101 return false; 1697 return false;
1102 } 1698 }
1103 } 1699 }
1105 virtual bool GetFileContent(MimeType& mime, 1701 virtual bool GetFileContent(MimeType& mime,
1106 std::string& content, 1702 std::string& content,
1107 boost::posix_time::ptime& modificationTime, 1703 boost::posix_time::ptime& modificationTime,
1108 const UriComponents& path) ORTHANC_OVERRIDE 1704 const UriComponents& path) ORTHANC_OVERRIDE
1109 { 1705 {
1110 if (!path.empty() && 1706 if (path.empty())
1111 path[0] == BY_UIDS) 1707 {
1708 return false;
1709 }
1710 else if (path[0] == BY_UIDS)
1112 { 1711 {
1113 if (path.size() == 3 && 1712 if (path.size() == 3 &&
1114 path[2] == "study.json") 1713 path[2] == "study.json")
1115 { 1714 {
1116 DatabaseLookup query; 1715 DatabaseLookup query;
1156 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */); 1755 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */);
1157 1756
1158 mime = MimeType_Dicom; 1757 mime = MimeType_Dicom;
1159 return visitor.IsSuccess(); 1758 return visitor.IsSuccess();
1160 } 1759 }
1760 }
1761 else if (path[0] == BY_PATIENTS)
1762 {
1763 return patients_.GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
1764 }
1765 else if (path[0] == BY_STUDIES)
1766 {
1767 return studies_.GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
1161 } 1768 }
1162 1769
1163 return false; 1770 return false;
1164 } 1771 }
1165 1772
1632 2239
1633 { 2240 {
1634 UriComponents root; // TODO 2241 UriComponents root; // TODO
1635 root.push_back("a"); 2242 root.push_back("a");
1636 root.push_back("b"); 2243 root.push_back("b");
1637 httpServer.Register(root, new WebDavStorage(true)); 2244 //httpServer.Register(root, new WebDavStorage(true));
1638 //httpServer.Register(root, new DummyBucket(context, true)); 2245 //httpServer.Register(root, new DummyBucket(context, true));
1639 //httpServer.Register(root, new DummyBucket2(context)); 2246 httpServer.Register(root, new DummyBucket2(context));
1640 } 2247 }
1641 2248
1642 if (httpServer.GetPortNumber() < 1024) 2249 if (httpServer.GetPortNumber() < 1024)
1643 { 2250 {
1644 LOG(WARNING) << "The HTTP port is privileged (" 2251 LOG(WARNING) << "The HTTP port is privileged ("