Mercurial > hg > orthanc
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 (" |