comparison OrthancServer/Sources/main.cpp @ 4240:799c0c527ced

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 09 Oct 2020 12:02:40 +0200
parents c8754c4c1862
children 5cfa6ba75dfc
comparison
equal deleted inserted replaced
4239:c8754c4c1862 4240:799c0c527ced
32 32
33 33
34 #include "PrecompiledHeadersServer.h" 34 #include "PrecompiledHeadersServer.h"
35 #include "OrthancRestApi/OrthancRestApi.h" 35 #include "OrthancRestApi/OrthancRestApi.h"
36 36
37 #include <boost/algorithm/string/predicate.hpp>
38
39 #include "../../OrthancFramework/Sources/Compatibility.h" 37 #include "../../OrthancFramework/Sources/Compatibility.h"
40 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" 38 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
41 #include "../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h" 39 #include "../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h"
42 #include "../../OrthancFramework/Sources/DicomNetworking/DicomServer.h" 40 #include "../../OrthancFramework/Sources/DicomNetworking/DicomServer.h"
43 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" 41 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
50 #include "OrthancConfiguration.h" 48 #include "OrthancConfiguration.h"
51 #include "OrthancFindRequestHandler.h" 49 #include "OrthancFindRequestHandler.h"
52 #include "OrthancGetRequestHandler.h" 50 #include "OrthancGetRequestHandler.h"
53 #include "OrthancInitialization.h" 51 #include "OrthancInitialization.h"
54 #include "OrthancMoveRequestHandler.h" 52 #include "OrthancMoveRequestHandler.h"
53 #include "OrthancWebDav.h"
55 #include "ServerContext.h" 54 #include "ServerContext.h"
56 #include "ServerJobs/StorageCommitmentScpJob.h" 55 #include "ServerJobs/StorageCommitmentScpJob.h"
57 #include "ServerToolbox.h" 56 #include "ServerToolbox.h"
58 #include "StorageCommitmentReports.h" 57 #include "StorageCommitmentReports.h"
59 58
60 #include "../../OrthancFramework/Sources/HttpServer/WebDavStorage.h" // TODO 59 #include <boost/algorithm/string/predicate.hpp>
61 #include "Search/DatabaseLookup.h" // TODO
62 #include <boost/regex.hpp> // TODO
63 60
64 61
65 using namespace Orthanc; 62 using namespace Orthanc;
66 63
67 64
611 std::string info = message.toStyledString(); 608 std::string info = message.toStyledString();
612 output.SendStatus(httpStatus, info); 609 output.SendStatus(httpStatus, info);
613 } 610 }
614 } 611 }
615 }; 612 };
616
617
618
619
620
621
622
623 static const char* const UPLOAD_FOLDER = "upload";
624
625 class DummyBucket : public IWebDavBucket // TODO
626 {
627 private:
628 ServerContext& context_;
629 WebDavStorage storage_;
630
631 bool IsUploadedFolder(const UriComponents& path) const
632 {
633 return (path.size() >= 1 && path[0] == UPLOAD_FOLDER);
634 }
635
636 public:
637 DummyBucket(ServerContext& context,
638 bool isMemory) :
639 context_(context),
640 storage_(isMemory)
641 {
642 }
643
644 virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE
645 {
646 if (IsUploadedFolder(path))
647 {
648 return storage_.IsExistingFolder(UriComponents(path.begin() + 1, path.end()));
649 }
650 else
651 {
652 return (path.size() == 0 ||
653 (path.size() == 1 && path[0] == "Folder1") ||
654 (path.size() == 2 && path[0] == "Folder1" && path[1] == "Folder2"));
655 }
656 }
657
658 virtual bool ListCollection(Collection& collection,
659 const UriComponents& path) ORTHANC_OVERRIDE
660 {
661 if (IsUploadedFolder(path))
662 {
663 return storage_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
664 }
665 else if (IsExistingFolder(path))
666 {
667 if (path.empty())
668 {
669 collection.AddResource(new Folder(UPLOAD_FOLDER));
670 }
671
672 for (unsigned int i = 0; i < 5; i++)
673 {
674 std::unique_ptr<File> f(new File("IM" + boost::lexical_cast<std::string>(i) + ".dcm"));
675 f->SetContentLength(1024 * i);
676 f->SetMimeType(MimeType_PlainText);
677 collection.AddResource(f.release());
678 }
679
680 for (unsigned int i = 0; i < 5; i++)
681 {
682 collection.AddResource(new Folder("Folder" + boost::lexical_cast<std::string>(i)));
683 }
684
685 return true;
686 }
687 else
688 {
689 return false;
690 }
691 }
692
693 virtual bool GetFileContent(MimeType& mime,
694 std::string& content,
695 boost::posix_time::ptime& time,
696 const UriComponents& path) ORTHANC_OVERRIDE
697 {
698 if (path.empty())
699 {
700 return false;
701 }
702 else if (IsUploadedFolder(path))
703 {
704 return storage_.GetFileContent(mime, content, time,
705 UriComponents(path.begin() + 1, path.end()));
706 }
707 else if (path.back() == "IM0.dcm" ||
708 path.back() == "IM1.dcm" ||
709 path.back() == "IM2.dcm" ||
710 path.back() == "IM3.dcm" ||
711 path.back() == "IM4.dcm")
712 {
713 time = boost::posix_time::second_clock::universal_time();
714
715 std::string s;
716 for (size_t i = 0; i < path.size(); i++)
717 {
718 s += "/" + path[i];
719 }
720
721 content = "Hello world!\r\n" + s + "\r\n";
722 mime = MimeType_PlainText;
723 return true;
724 }
725 else
726 {
727 return false;
728 }
729 }
730
731
732 virtual bool StoreFile(const std::string& content,
733 const UriComponents& path) ORTHANC_OVERRIDE
734 {
735 if (IsUploadedFolder(path))
736 {
737 return storage_.StoreFile(content, UriComponents(path.begin() + 1, path.end()));
738 }
739 else
740 {
741 LOG(WARNING) << "Writing to a read-only location in WebDAV: " << Toolbox::FlattenUri(path);
742 return false;
743 }
744 }
745
746
747 virtual bool CreateFolder(const UriComponents& path) ORTHANC_OVERRIDE
748 {
749 if (IsUploadedFolder(path))
750 {
751 return storage_.CreateFolder(UriComponents(path.begin() + 1, path.end()));
752 }
753 else
754 {
755 LOG(WARNING) << "Writing to a read-only location in WebDAV: " << Toolbox::FlattenUri(path);
756 return false;
757 }
758 }
759
760 virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE
761 {
762 return false; // read-only
763 }
764
765 virtual void Start() ORTHANC_OVERRIDE
766 {
767 LOG(WARNING) << "Starting WebDAV";
768 }
769
770 virtual void Stop() ORTHANC_OVERRIDE
771 {
772 LOG(WARNING) << "Stopping WebDAV";
773 }
774 };
775
776
777
778
779
780 static const char* const BY_PATIENTS = "by-patients";
781 static const char* const BY_STUDIES = "by-studies";
782 static const char* const BY_DATE = "by-dates";
783 static const char* const BY_UIDS = "by-uids";
784 static const char* const UPLOADS = "uploads";
785 static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
786
787 class DummyBucket2 : public IWebDavBucket // TODO
788 {
789 private:
790 typedef std::map<ResourceType, std::string> Templates;
791
792
793 static boost::posix_time::ptime GetNow()
794 {
795 return boost::posix_time::second_clock::universal_time();
796 }
797
798
799 static void LookupTime(boost::posix_time::ptime& target,
800 ServerContext& context,
801 const std::string& publicId,
802 MetadataType metadata)
803 {
804 std::string value;
805 if (context.GetIndex().LookupMetadata(value, publicId, metadata))
806 {
807 try
808 {
809 target = boost::posix_time::from_iso_string(value);
810 return;
811 }
812 catch (std::exception& e)
813 {
814 }
815 }
816
817 target = GetNow();
818 }
819
820
821 class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor
822 {
823 private:
824 ServerContext& context_;
825 bool isComplete_;
826 Collection& target_;
827 ResourceType level_;
828
829 public:
830 DicomIdentifiersVisitor(ServerContext& context,
831 Collection& target,
832 ResourceType level) :
833 context_(context),
834 isComplete_(false),
835 target_(target),
836 level_(level)
837 {
838 }
839
840 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
841 {
842 return false; // (*)
843 }
844
845 virtual void MarkAsComplete() ORTHANC_OVERRIDE
846 {
847 isComplete_ = true; // TODO
848 }
849
850 virtual void Visit(const std::string& publicId,
851 const std::string& instanceId /* unused */,
852 const DicomMap& mainDicomTags,
853 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE
854 {
855 DicomTag tag(0, 0);
856 MetadataType timeMetadata;
857
858 switch (level_)
859 {
860 case ResourceType_Study:
861 tag = DICOM_TAG_STUDY_INSTANCE_UID;
862 timeMetadata = MetadataType_LastUpdate;
863 break;
864
865 case ResourceType_Series:
866 tag = DICOM_TAG_SERIES_INSTANCE_UID;
867 timeMetadata = MetadataType_LastUpdate;
868 break;
869
870 case ResourceType_Instance:
871 tag = DICOM_TAG_SOP_INSTANCE_UID;
872 timeMetadata = MetadataType_Instance_ReceptionDate;
873 break;
874
875 default:
876 throw OrthancException(ErrorCode_InternalError);
877 }
878
879 std::string s;
880 if (mainDicomTags.LookupStringValue(s, tag, false) &&
881 !s.empty())
882 {
883 std::unique_ptr<Resource> resource;
884
885 if (level_ == ResourceType_Instance)
886 {
887 FileInfo info;
888 if (context_.GetIndex().LookupAttachment(info, publicId, FileContentType_Dicom))
889 {
890 std::unique_ptr<File> f(new File(s + ".dcm"));
891 f->SetMimeType(MimeType_Dicom);
892 f->SetContentLength(info.GetUncompressedSize());
893 resource.reset(f.release());
894 }
895 }
896 else
897 {
898 resource.reset(new Folder(s));
899 }
900
901 if (resource.get() != NULL)
902 {
903 boost::posix_time::ptime t;
904 LookupTime(t, context_, publicId, timeMetadata);
905 resource->SetCreationTime(t);
906 target_.AddResource(resource.release());
907 }
908 }
909 }
910 };
911
912 class DicomFileVisitor : public ServerContext::ILookupVisitor
913 {
914 private:
915 ServerContext& context_;
916 bool success_;
917 std::string& target_;
918 boost::posix_time::ptime& time_;
919
920 public:
921 DicomFileVisitor(ServerContext& context,
922 std::string& target,
923 boost::posix_time::ptime& time) :
924 context_(context),
925 success_(false),
926 target_(target),
927 time_(time)
928 {
929 }
930
931 bool IsSuccess() const
932 {
933 return success_;
934 }
935
936 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
937 {
938 return false; // (*)
939 }
940
941 virtual void MarkAsComplete() ORTHANC_OVERRIDE
942 {
943 }
944
945 virtual void Visit(const std::string& publicId,
946 const std::string& instanceId /* unused */,
947 const DicomMap& mainDicomTags,
948 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE
949 {
950 if (success_)
951 {
952 success_ = false; // Two matches => Error
953 }
954 else
955 {
956 LookupTime(time_, context_, publicId, MetadataType_Instance_ReceptionDate);
957 context_.ReadDicom(target_, publicId);
958 success_ = true;
959 }
960 }
961 };
962
963 class OrthancJsonVisitor : public ServerContext::ILookupVisitor
964 {
965 private:
966 ServerContext& context_;
967 bool success_;
968 std::string& target_;
969 ResourceType level_;
970
971 public:
972 OrthancJsonVisitor(ServerContext& context,
973 std::string& target,
974 ResourceType level) :
975 context_(context),
976 success_(false),
977 target_(target),
978 level_(level)
979 {
980 }
981
982 bool IsSuccess() const
983 {
984 return success_;
985 }
986
987 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
988 {
989 return false; // (*)
990 }
991
992 virtual void MarkAsComplete() ORTHANC_OVERRIDE
993 {
994 }
995
996 virtual void Visit(const std::string& publicId,
997 const std::string& instanceId /* unused */,
998 const DicomMap& mainDicomTags,
999 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE
1000 {
1001 Json::Value info;
1002 if (context_.GetIndex().LookupResource(info, publicId, level_))
1003 {
1004 if (success_)
1005 {
1006 success_ = false; // Two matches => Error
1007 }
1008 else
1009 {
1010 target_ = info.toStyledString();
1011
1012 // Replace UNIX newlines with DOS newlines
1013 boost::replace_all(target_, "\n", "\r\n");
1014
1015 success_ = true;
1016 }
1017 }
1018 }
1019 };
1020
1021
1022 void AddVirtualFile(Collection& collection,
1023 const UriComponents& path,
1024 const std::string& filename)
1025 {
1026 MimeType mime;
1027 std::string content;
1028 boost::posix_time::ptime modification;
1029
1030 UriComponents p = path;
1031 p.push_back(filename);
1032
1033 if (GetFileContent(mime, content, modification, p))
1034 {
1035 std::unique_ptr<File> f(new File(filename));
1036 f->SetMimeType(mime);
1037 f->SetContentLength(content.size());
1038 f->SetCreationTime(modification);
1039 collection.AddResource(f.release());
1040 }
1041 }
1042
1043
1044
1045
1046 class ResourcesIndex : public boost::noncopyable
1047 {
1048 public:
1049 typedef std::map<std::string, std::string> Map;
1050
1051 private:
1052 ServerContext& context_;
1053 ResourceType level_;
1054 std::string template_;
1055 Map pathToResource_;
1056 Map resourceToPath_;
1057
1058 void CheckInvariants()
1059 {
1060 #ifndef NDEBUG
1061 assert(pathToResource_.size() == resourceToPath_.size());
1062
1063 for (Map::const_iterator it = pathToResource_.begin(); it != pathToResource_.end(); ++it)
1064 {
1065 assert(resourceToPath_[it->second] == it->first);
1066 }
1067
1068 for (Map::const_iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it)
1069 {
1070 assert(pathToResource_[it->second] == it->first);
1071 }
1072 #endif
1073 }
1074
1075 void AddTags(DicomMap& target,
1076 const std::string& resourceId,
1077 ResourceType tagsFromLevel)
1078 {
1079 DicomMap tags;
1080 if (context_.GetIndex().GetMainDicomTags(tags, resourceId, level_, tagsFromLevel))
1081 {
1082 target.Merge(tags);
1083 }
1084 }
1085
1086 void Register(const std::string& resourceId)
1087 {
1088 // Don't register twice the same resource
1089 if (resourceToPath_.find(resourceId) == resourceToPath_.end())
1090 {
1091 std::string name = template_;
1092
1093 DicomMap tags;
1094
1095 AddTags(tags, resourceId, level_);
1096
1097 if (level_ == ResourceType_Study)
1098 {
1099 AddTags(tags, resourceId, ResourceType_Patient);
1100 }
1101
1102 DicomArray arr(tags);
1103 for (size_t i = 0; i < arr.GetSize(); i++)
1104 {
1105 const DicomElement& element = arr.GetElement(i);
1106 if (!element.GetValue().IsNull() &&
1107 !element.GetValue().IsBinary())
1108 {
1109 const std::string tag = FromDcmtkBridge::GetTagName(element.GetTag(), "");
1110 boost::replace_all(name, "{{" + tag + "}}", element.GetValue().GetContent());
1111 }
1112 }
1113
1114 // Blank the tags that were not matched
1115 static const boost::regex REGEX_BLANK_TAGS("{{.*?}}"); // non-greedy match
1116 name = boost::regex_replace(name, REGEX_BLANK_TAGS, "");
1117
1118 // UTF-8 characters cannot be used on Windows XP
1119 name = Toolbox::ConvertToAscii(name);
1120 boost::replace_all(name, "/", "");
1121 boost::replace_all(name, "\\", "");
1122
1123 // Trim sequences of spaces as one single space
1124 static const boost::regex REGEX_TRIM_SPACES("{{.*?}}");
1125 name = boost::regex_replace(name, REGEX_TRIM_SPACES, " ");
1126 name = Toolbox::StripSpaces(name);
1127
1128 size_t count = 0;
1129 for (;;)
1130 {
1131 std::string path = name;
1132 if (count > 0)
1133 {
1134 path += " (" + boost::lexical_cast<std::string>(count) + ")";
1135 }
1136
1137 if (pathToResource_.find(path) == pathToResource_.end())
1138 {
1139 pathToResource_[path] = resourceId;
1140 resourceToPath_[resourceId] = path;
1141 return;
1142 }
1143
1144 count++;
1145 }
1146
1147 throw OrthancException(ErrorCode_InternalError);
1148 }
1149 }
1150
1151 public:
1152 ResourcesIndex(ServerContext& context,
1153 ResourceType level,
1154 const std::string& templateString) :
1155 context_(context),
1156 level_(level),
1157 template_(templateString)
1158 {
1159 }
1160
1161 ResourceType GetLevel() const
1162 {
1163 return level_;
1164 }
1165
1166 void Refresh(std::set<std::string>& removedPaths /* out */,
1167 const std::set<std::string>& resources)
1168 {
1169 CheckInvariants();
1170
1171 // Detect the resources that have been removed since last refresh
1172 removedPaths.clear();
1173 std::set<std::string> removedResources;
1174
1175 for (Map::iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it)
1176 {
1177 if (resources.find(it->first) == resources.end())
1178 {
1179 const std::string& path = it->second;
1180
1181 assert(pathToResource_.find(path) != pathToResource_.end());
1182 pathToResource_.erase(path);
1183 removedPaths.insert(path);
1184
1185 removedResources.insert(it->first); // Delay the removal to avoid disturbing the iterator
1186 }
1187 }
1188
1189 // Remove the missing resources
1190 for (std::set<std::string>::const_iterator it = removedResources.begin(); it != removedResources.end(); ++it)
1191 {
1192 assert(resourceToPath_.find(*it) != resourceToPath_.end());
1193 resourceToPath_.erase(*it);
1194 }
1195
1196 CheckInvariants();
1197
1198 for (std::set<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it)
1199 {
1200 Register(*it);
1201 }
1202
1203 CheckInvariants();
1204 }
1205
1206 const Map& GetPathToResource() const
1207 {
1208 return pathToResource_;
1209 }
1210 };
1211
1212
1213 class INode : public boost::noncopyable
1214 {
1215 public:
1216 virtual ~INode()
1217 {
1218 }
1219
1220 virtual bool ListCollection(IWebDavBucket::Collection& target,
1221 const UriComponents& path) = 0;
1222
1223 virtual bool GetFileContent(MimeType& mime,
1224 std::string& content,
1225 boost::posix_time::ptime& time,
1226 const UriComponents& path) = 0;
1227 };
1228
1229
1230
1231 class InstancesOfSeries : public INode
1232 {
1233 private:
1234 ServerContext& context_;
1235 std::string parentSeries_;
1236
1237 public:
1238 InstancesOfSeries(ServerContext& context,
1239 const std::string& parentSeries) :
1240 context_(context),
1241 parentSeries_(parentSeries)
1242 {
1243 }
1244
1245 virtual bool ListCollection(IWebDavBucket::Collection& target,
1246 const UriComponents& path) ORTHANC_OVERRIDE
1247 {
1248 if (path.empty())
1249 {
1250 std::list<std::string> resources;
1251 try
1252 {
1253 context_.GetIndex().GetChildren(resources, parentSeries_);
1254 }
1255 catch (OrthancException&)
1256 {
1257 // Unknown (or deleted) parent series
1258 return false;
1259 }
1260
1261 for (std::list<std::string>::const_iterator
1262 it = resources.begin(); it != resources.end(); ++it)
1263 {
1264 boost::posix_time::ptime time;
1265 LookupTime(time, context_, *it, MetadataType_Instance_ReceptionDate);
1266
1267 FileInfo info;
1268 if (context_.GetIndex().LookupAttachment(info, *it, FileContentType_Dicom))
1269 {
1270 std::unique_ptr<File> resource(new File(*it + ".dcm"));
1271 resource->SetMimeType(MimeType_Dicom);
1272 resource->SetContentLength(info.GetUncompressedSize());
1273 resource->SetCreationTime(time);
1274 target.AddResource(resource.release());
1275 }
1276 }
1277
1278 return true;
1279 }
1280 else
1281 {
1282 return false;
1283 }
1284 }
1285
1286 virtual bool GetFileContent(MimeType& mime,
1287 std::string& content,
1288 boost::posix_time::ptime& time,
1289 const UriComponents& path) ORTHANC_OVERRIDE
1290 {
1291 if (path.size() == 1 &&
1292 boost::ends_with(path[0], ".dcm"))
1293 {
1294 std::string instanceId = path[0].substr(0, path[0].size() - 4);
1295
1296 try
1297 {
1298 mime = MimeType_Dicom;
1299 context_.ReadDicom(content, instanceId);
1300 LookupTime(time, context_, instanceId, MetadataType_Instance_ReceptionDate);
1301 return true;
1302 }
1303 catch (OrthancException&)
1304 {
1305 // File was removed
1306 return false;
1307 }
1308 }
1309 else
1310 {
1311 return false;
1312 }
1313 }
1314 };
1315
1316
1317
1318 /**
1319 * The "InternalNode" class corresponds to a non-leaf node in the
1320 * WebDAV tree, that only contains subfolders (no file).
1321 *
1322 * TODO: Implement a LRU index to dynamically remove the oldest
1323 * children on high RAM usage.
1324 **/
1325 class InternalNode : public INode
1326 {
1327 private:
1328 typedef std::map<std::string, INode*> Children;
1329
1330 Children children_;
1331
1332 INode* GetChild(const std::string& path) // Don't delete the result pointer!
1333 {
1334 Children::const_iterator child = children_.find(path);
1335 if (child == children_.end())
1336 {
1337 INode* child = CreateChild(path);
1338
1339 if (child == NULL)
1340 {
1341 return NULL;
1342 }
1343 else
1344 {
1345 children_[path] = child;
1346 return child;
1347 }
1348 }
1349 else
1350 {
1351 assert(child->second != NULL);
1352 return child->second;
1353 }
1354 }
1355
1356 protected:
1357 void RemoveSubfolder(const std::string& path)
1358 {
1359 Children::iterator child = children_.find(path);
1360 if (child != children_.end())
1361 {
1362 assert(child->second != NULL);
1363 delete child->second;
1364 children_.erase(child);
1365 }
1366 }
1367
1368 virtual void Refresh() = 0;
1369
1370 virtual bool ListSubfolders(IWebDavBucket::Collection& target) = 0;
1371
1372 virtual INode* CreateChild(const std::string& path) = 0;
1373
1374 public:
1375 virtual ~InternalNode()
1376 {
1377 for (Children::iterator it = children_.begin(); it != children_.end(); ++it)
1378 {
1379 assert(it->second != NULL);
1380 delete it->second;
1381 }
1382 }
1383
1384 virtual bool ListCollection(IWebDavBucket::Collection& target,
1385 const UriComponents& path)
1386 ORTHANC_OVERRIDE ORTHANC_FINAL
1387 {
1388 Refresh();
1389
1390 if (path.empty())
1391 {
1392 return ListSubfolders(target);
1393 }
1394 else
1395 {
1396 // Recursivity
1397 INode* child = GetChild(path[0]);
1398 if (child == NULL)
1399 {
1400 return false;
1401 }
1402 else
1403 {
1404 UriComponents subpath(path.begin() + 1, path.end());
1405 return child->ListCollection(target, subpath);
1406 }
1407 }
1408 }
1409
1410 virtual bool GetFileContent(MimeType& mime,
1411 std::string& content,
1412 boost::posix_time::ptime& time,
1413 const UriComponents& path)
1414 ORTHANC_OVERRIDE ORTHANC_FINAL
1415 {
1416 if (path.empty())
1417 {
1418 return false; // An internal node doesn't correspond to a file
1419 }
1420 else
1421 {
1422 // Recursivity
1423 Refresh();
1424
1425 INode* child = GetChild(path[0]);
1426 if (child == NULL)
1427 {
1428 return false;
1429 }
1430 else
1431 {
1432 UriComponents subpath(path.begin() + 1, path.end());
1433 return child->GetFileContent(mime, content, time, subpath);
1434 }
1435 }
1436 }
1437 };
1438
1439
1440 class ListOfResources : public InternalNode
1441 {
1442 private:
1443 ServerContext& context_;
1444 const Templates& templates_;
1445 std::unique_ptr<ResourcesIndex> index_;
1446 MetadataType timeMetadata_;
1447
1448 protected:
1449 virtual void Refresh() ORTHANC_OVERRIDE ORTHANC_FINAL
1450 {
1451 std::list<std::string> resources;
1452 GetCurrentResources(resources);
1453
1454 std::set<std::string> removedPaths;
1455 index_->Refresh(removedPaths, std::set<std::string>(resources.begin(), resources.end()));
1456
1457 // Remove the children whose associated resource doesn't exist anymore
1458 for (std::set<std::string>::const_iterator
1459 it = removedPaths.begin(); it != removedPaths.end(); ++it)
1460 {
1461 RemoveSubfolder(*it);
1462 }
1463 }
1464
1465 virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE ORTHANC_FINAL
1466 {
1467 if (index_->GetLevel() == ResourceType_Instance)
1468 {
1469 // Not a collection, no subfolders
1470 return false;
1471 }
1472 else
1473 {
1474 const ResourcesIndex::Map& paths = index_->GetPathToResource();
1475
1476 for (ResourcesIndex::Map::const_iterator it = paths.begin(); it != paths.end(); ++it)
1477 {
1478 boost::posix_time::ptime time;
1479 LookupTime(time, context_, it->second, timeMetadata_);
1480
1481 std::unique_ptr<IWebDavBucket::Resource> resource(new IWebDavBucket::Folder(it->first));
1482 resource->SetCreationTime(time);
1483 target.AddResource(resource.release());
1484 }
1485
1486 return true;
1487 }
1488 }
1489
1490 virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE ORTHANC_FINAL
1491 {
1492 ResourcesIndex::Map::const_iterator resource = index_->GetPathToResource().find(path);
1493 if (resource == index_->GetPathToResource().end())
1494 {
1495 return NULL;
1496 }
1497 else
1498 {
1499 return CreateResourceNode(resource->second);
1500 }
1501 }
1502
1503 ServerContext& GetContext() const
1504 {
1505 return context_;
1506 }
1507
1508 virtual void GetCurrentResources(std::list<std::string>& resources) = 0;
1509
1510 virtual INode* CreateResourceNode(const std::string& resource) = 0;
1511
1512 public:
1513 ListOfResources(ServerContext& context,
1514 ResourceType level,
1515 const Templates& templates) :
1516 context_(context),
1517 templates_(templates)
1518 {
1519 Templates::const_iterator t = templates.find(level);
1520 if (t == templates.end())
1521 {
1522 throw OrthancException(ErrorCode_ParameterOutOfRange);
1523 }
1524
1525 index_.reset(new ResourcesIndex(context, level, t->second));
1526
1527 if (level == ResourceType_Instance)
1528 {
1529 timeMetadata_ = MetadataType_Instance_ReceptionDate;
1530 }
1531 else
1532 {
1533 timeMetadata_ = MetadataType_LastUpdate;
1534 }
1535 }
1536
1537 ResourceType GetLevel() const
1538 {
1539 return index_->GetLevel();
1540 }
1541
1542 const Templates& GetTemplates() const
1543 {
1544 return templates_;
1545 }
1546 };
1547
1548
1549
1550 class SingleDicomResource : public ListOfResources
1551 {
1552 private:
1553 std::string parentId_;
1554
1555 protected:
1556 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
1557 {
1558 try
1559 {
1560 GetContext().GetIndex().GetChildren(resources, parentId_);
1561 }
1562 catch (OrthancException&)
1563 {
1564 // Unknown parent resource
1565 resources.clear();
1566 }
1567 }
1568
1569 virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE
1570 {
1571 if (GetLevel() == ResourceType_Instance)
1572 {
1573 return NULL;
1574 }
1575 else if (GetLevel() == ResourceType_Series)
1576 {
1577 return new InstancesOfSeries(GetContext(), resource);
1578 }
1579 else
1580 {
1581 ResourceType l = GetChildResourceType(GetLevel());
1582 return new SingleDicomResource(GetContext(), l, resource, GetTemplates());
1583 }
1584 }
1585
1586 public:
1587 SingleDicomResource(ServerContext& context,
1588 ResourceType level,
1589 const std::string& parentId,
1590 const Templates& templates) :
1591 ListOfResources(context, level, templates),
1592 parentId_(parentId)
1593 {
1594 }
1595 };
1596
1597
1598 class RootNode : public ListOfResources
1599 {
1600 protected:
1601 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
1602 {
1603 GetContext().GetIndex().GetAllUuids(resources, GetLevel());
1604 }
1605
1606 virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE
1607 {
1608 if (GetLevel() == ResourceType_Series)
1609 {
1610 return new InstancesOfSeries(GetContext(), resource);
1611 }
1612 else
1613 {
1614 ResourceType l = GetChildResourceType(GetLevel());
1615 return new SingleDicomResource(GetContext(), l, resource, GetTemplates());
1616 }
1617 }
1618
1619 public:
1620 RootNode(ServerContext& context,
1621 ResourceType level,
1622 const Templates& templates) :
1623 ListOfResources(context, level, templates)
1624 {
1625 }
1626 };
1627
1628
1629 class ListOfStudiesByDate : public ListOfResources
1630 {
1631 private:
1632 std::string year_;
1633 std::string month_;
1634
1635 class Visitor : public ServerContext::ILookupVisitor
1636 {
1637 private:
1638 std::list<std::string>& resources_;
1639
1640 public:
1641 Visitor(std::list<std::string>& resources) :
1642 resources_(resources)
1643 {
1644 }
1645
1646 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
1647 {
1648 return false; // (*)
1649 }
1650
1651 virtual void MarkAsComplete() ORTHANC_OVERRIDE
1652 {
1653 }
1654
1655 virtual void Visit(const std::string& publicId,
1656 const std::string& instanceId /* unused */,
1657 const DicomMap& mainDicomTags,
1658 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE
1659 {
1660 resources_.push_back(publicId);
1661 }
1662 };
1663
1664 protected:
1665 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE
1666 {
1667 DatabaseLookup query;
1668 query.AddRestConstraint(DICOM_TAG_STUDY_DATE, year_ + month_ + "01-" + year_ + month_ + "31",
1669 true /* case sensitive */, true /* mandatory tag */);
1670
1671 Visitor visitor(resources);
1672 GetContext().Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
1673 }
1674
1675 virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE
1676 {
1677 return new SingleDicomResource(GetContext(), ResourceType_Series, resource, GetTemplates());
1678 }
1679
1680 public:
1681 ListOfStudiesByDate(ServerContext& context,
1682 const std::string& year,
1683 const std::string& month,
1684 const Templates& templates) :
1685 ListOfResources(context, ResourceType_Study, templates),
1686 year_(year),
1687 month_(month)
1688 {
1689 if (year.size() != 4 ||
1690 month.size() != 2)
1691 {
1692 throw OrthancException(ErrorCode_ParameterOutOfRange);
1693 }
1694 }
1695 };
1696
1697
1698 class ListOfStudiesByMonth : public InternalNode
1699 {
1700 private:
1701 ServerContext& context_;
1702 std::string year_;
1703 const Templates& templates_;
1704
1705 class Visitor : public ServerContext::ILookupVisitor
1706 {
1707 private:
1708 std::set<std::string> months_;
1709
1710 public:
1711 Visitor()
1712 {
1713 }
1714
1715 const std::set<std::string>& GetMonths() const
1716 {
1717 return months_;
1718 }
1719
1720 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
1721 {
1722 return false; // (*)
1723 }
1724
1725 virtual void MarkAsComplete() ORTHANC_OVERRIDE
1726 {
1727 }
1728
1729 virtual void Visit(const std::string& publicId,
1730 const std::string& instanceId /* unused */,
1731 const DicomMap& mainDicomTags,
1732 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE
1733 {
1734 std::string s;
1735 if (mainDicomTags.LookupStringValue(s, DICOM_TAG_STUDY_DATE, false) &&
1736 s.size() == 8)
1737 {
1738 months_.insert(s.substr(4, 2)); // Get the month from "YYYYMMDD"
1739 }
1740 }
1741 };
1742
1743 protected:
1744 virtual void Refresh() ORTHANC_OVERRIDE
1745 {
1746 }
1747
1748 virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE
1749 {
1750 DatabaseLookup query;
1751 query.AddRestConstraint(DICOM_TAG_STUDY_DATE, year_ + "0101-" + year_ + "1231",
1752 true /* case sensitive */, true /* mandatory tag */);
1753
1754 Visitor visitor;
1755 context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
1756
1757 for (std::set<std::string>::const_iterator it = visitor.GetMonths().begin();
1758 it != visitor.GetMonths().end(); ++it)
1759 {
1760 target.AddResource(new IWebDavBucket::Folder(year_ + "-" + *it));
1761 }
1762
1763 return true;
1764 }
1765
1766 virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE
1767 {
1768 if (path.size() != 7) // Format: "YYYY-MM"
1769 {
1770 throw OrthancException(ErrorCode_InternalError);
1771 }
1772 else
1773 {
1774 const std::string year = path.substr(0, 4);
1775 const std::string month = path.substr(5, 2);
1776 return new ListOfStudiesByDate(context_, year, month, templates_);
1777 }
1778 }
1779
1780 public:
1781 ListOfStudiesByMonth(ServerContext& context,
1782 const std::string& year,
1783 const Templates& templates) :
1784 context_(context),
1785 year_(year),
1786 templates_(templates)
1787 {
1788 if (year_.size() != 4)
1789 {
1790 throw OrthancException(ErrorCode_ParameterOutOfRange);
1791 }
1792 }
1793 };
1794
1795
1796 class ListOfStudiesByYear : public InternalNode
1797 {
1798 private:
1799 ServerContext& context_;
1800 const Templates& templates_;
1801
1802 protected:
1803 virtual void Refresh() ORTHANC_OVERRIDE
1804 {
1805 }
1806
1807 virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE
1808 {
1809 std::list<std::string> resources;
1810 context_.GetIndex().GetAllUuids(resources, ResourceType_Study);
1811
1812 std::set<std::string> years;
1813
1814 for (std::list<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it)
1815 {
1816 DicomMap tags;
1817 std::string studyDate;
1818 if (context_.GetIndex().GetMainDicomTags(tags, *it, ResourceType_Study, ResourceType_Study) &&
1819 tags.LookupStringValue(studyDate, DICOM_TAG_STUDY_DATE, false) &&
1820 studyDate.size() == 8)
1821 {
1822 years.insert(studyDate.substr(0, 4)); // Get the year from "YYYYMMDD"
1823 }
1824 }
1825
1826 for (std::set<std::string>::const_iterator it = years.begin(); it != years.end(); ++it)
1827 {
1828 target.AddResource(new IWebDavBucket::Folder(*it));
1829 }
1830
1831 return true;
1832 }
1833
1834 virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE
1835 {
1836 return new ListOfStudiesByMonth(context_, path, templates_);
1837 }
1838
1839 public:
1840 ListOfStudiesByYear(ServerContext& context,
1841 const Templates& templates) :
1842 context_(context),
1843 templates_(templates)
1844 {
1845 }
1846 };
1847
1848
1849 static void UploadWorker(DummyBucket2* that)
1850 {
1851 assert(that != NULL);
1852
1853 boost::posix_time::ptime lastModification = GetNow();
1854
1855 while (that->running_)
1856 {
1857 std::unique_ptr<IDynamicObject> obj(that->uploadQueue_.Dequeue(100));
1858 if (obj.get() != NULL)
1859 {
1860 that->Upload(reinterpret_cast<const SingleValueObject<std::string>&>(*obj).GetValue());
1861 lastModification = GetNow();
1862 }
1863 else if (GetNow() - lastModification > boost::posix_time::seconds(10))
1864 {
1865 // After every 10 seconds of inactivity, remove the empty folders
1866 LOG(INFO) << "Cleaning up the empty WebDAV upload folders";
1867 that->uploads_.RemoveEmptyFolders();
1868 lastModification = GetNow();
1869 }
1870 }
1871 }
1872
1873 void Upload(const std::string& path)
1874 {
1875 UriComponents uri;
1876 Toolbox::SplitUriComponents(uri, path);
1877
1878 LOG(INFO) << "Upload from WebDAV: " << path;
1879
1880 MimeType mime;
1881 std::string content;
1882 boost::posix_time::ptime time;
1883 if (uploads_.GetFileContent(mime, content, time, uri))
1884 {
1885 DicomInstanceToStore instance;
1886 // instance.SetOrigin(DicomInstanceOrigin_WebDav);
1887 instance.SetBuffer(content.c_str(), content.size());
1888
1889 std::string publicId;
1890 StoreStatus status = context_.Store(publicId, instance, StoreInstanceMode_Default);
1891 if (status == StoreStatus_Success ||
1892 status == StoreStatus_AlreadyStored)
1893 {
1894 LOG(INFO) << "Successfully imported DICOM instance from WebDAV: " << path << " (Orthanc ID: " << publicId << ")";
1895 uploads_.DeleteItem(uri);
1896 }
1897 else
1898 {
1899 LOG(WARNING) << "Cannot import DICOM instance from WebWAV: " << path;
1900 }
1901 }
1902 }
1903
1904
1905 ServerContext& context_;
1906 std::unique_ptr<INode> patients_;
1907 std::unique_ptr<INode> studies_;
1908 std::unique_ptr<INode> dates_;
1909 Templates patientsTemplates_;
1910 Templates studiesTemplates_;
1911 WebDavStorage uploads_;
1912 SharedMessageQueue uploadQueue_;
1913 boost::thread uploadThread_;
1914 bool running_;
1915
1916 public:
1917 DummyBucket2(ServerContext& context) :
1918 context_(context),
1919 uploads_(false /* store uploads as temporary files */),
1920 running_(false)
1921 {
1922 patientsTemplates_[ResourceType_Patient] = "{{PatientID}} - {{PatientName}}";
1923 patientsTemplates_[ResourceType_Study] = "{{StudyDate}} - {{StudyDescription}}";
1924 patientsTemplates_[ResourceType_Series] = "{{Modality}} - {{SeriesDescription}}";
1925
1926 studiesTemplates_[ResourceType_Study] = "{{PatientID}} - {{PatientName}} - {{StudyDescription}}";
1927 studiesTemplates_[ResourceType_Series] = patientsTemplates_[ResourceType_Series];
1928
1929 patients_.reset(new RootNode(context, ResourceType_Patient, patientsTemplates_));
1930 studies_.reset(new RootNode(context, ResourceType_Study, studiesTemplates_));
1931 dates_.reset(new ListOfStudiesByYear(context, studiesTemplates_));
1932 }
1933
1934 virtual ~DummyBucket2()
1935 {
1936 Stop();
1937 }
1938
1939 virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE
1940 {
1941 if (path.empty())
1942 {
1943 return true;
1944 }
1945 else if (path[0] == BY_UIDS)
1946 {
1947 return (path.size() <= 3 &&
1948 (path.size() != 3 || path[2] != "study.json"));
1949 }
1950 else if (path[0] == BY_PATIENTS)
1951 {
1952 IWebDavBucket::Collection tmp;
1953 return patients_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
1954 }
1955 else if (path[0] == BY_STUDIES)
1956 {
1957 IWebDavBucket::Collection tmp;
1958 return studies_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
1959 }
1960 else if (path[0] == BY_DATE)
1961 {
1962 IWebDavBucket::Collection tmp;
1963 return dates_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end()));
1964 }
1965 else if (path[0] == UPLOADS)
1966 {
1967 return uploads_.IsExistingFolder(UriComponents(path.begin() + 1, path.end()));
1968 }
1969 else
1970 {
1971 return false;
1972 }
1973 }
1974
1975 virtual bool ListCollection(Collection& collection,
1976 const UriComponents& path) ORTHANC_OVERRIDE
1977 {
1978 if (path.empty())
1979 {
1980 collection.AddResource(new Folder(BY_DATE));
1981 collection.AddResource(new Folder(BY_PATIENTS));
1982 collection.AddResource(new Folder(BY_STUDIES));
1983 collection.AddResource(new Folder(BY_UIDS));
1984 collection.AddResource(new Folder(UPLOADS));
1985 return true;
1986 }
1987 else if (path[0] == BY_UIDS)
1988 {
1989 DatabaseLookup query;
1990 ResourceType level;
1991 size_t limit = 0; // By default, no limits
1992
1993 if (path.size() == 1)
1994 {
1995 level = ResourceType_Study;
1996 limit = 100; // TODO
1997 }
1998 else if (path.size() == 2)
1999 {
2000 AddVirtualFile(collection, path, "study.json");
2001
2002 level = ResourceType_Series;
2003 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
2004 true /* case sensitive */, true /* mandatory tag */);
2005 }
2006 else if (path.size() == 3)
2007 {
2008 AddVirtualFile(collection, path, "series.json");
2009
2010 level = ResourceType_Instance;
2011 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
2012 true /* case sensitive */, true /* mandatory tag */);
2013 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
2014 true /* case sensitive */, true /* mandatory tag */);
2015 }
2016 else
2017 {
2018 return false;
2019 }
2020
2021 DicomIdentifiersVisitor visitor(context_, collection, level);
2022 context_.Apply(visitor, query, level, 0 /* since */, limit);
2023
2024 return true;
2025 }
2026 else if (path[0] == BY_PATIENTS)
2027 {
2028 return patients_->ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
2029 }
2030 else if (path[0] == BY_STUDIES)
2031 {
2032 return studies_->ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
2033 }
2034 else if (path[0] == BY_DATE)
2035 {
2036 return dates_->ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
2037 }
2038 else if (path[0] == UPLOADS)
2039 {
2040 return uploads_.ListCollection(collection, UriComponents(path.begin() + 1, path.end()));
2041 }
2042 else
2043 {
2044 return false;
2045 }
2046 }
2047
2048 virtual bool GetFileContent(MimeType& mime,
2049 std::string& content,
2050 boost::posix_time::ptime& modificationTime,
2051 const UriComponents& path) ORTHANC_OVERRIDE
2052 {
2053 if (path.empty())
2054 {
2055 return false;
2056 }
2057 else if (path[0] == BY_UIDS)
2058 {
2059 if (path.size() == 3 &&
2060 path[2] == "study.json")
2061 {
2062 DatabaseLookup query;
2063 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
2064 true /* case sensitive */, true /* mandatory tag */);
2065
2066 OrthancJsonVisitor visitor(context_, content, ResourceType_Study);
2067 context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */);
2068
2069 mime = MimeType_Json;
2070 return visitor.IsSuccess();
2071 }
2072 else if (path.size() == 4 &&
2073 path[3] == "series.json")
2074 {
2075 DatabaseLookup query;
2076 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
2077 true /* case sensitive */, true /* mandatory tag */);
2078 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
2079 true /* case sensitive */, true /* mandatory tag */);
2080
2081 OrthancJsonVisitor visitor(context_, content, ResourceType_Series);
2082 context_.Apply(visitor, query, ResourceType_Series, 0 /* since */, 0 /* no limit */);
2083
2084 mime = MimeType_Json;
2085 return visitor.IsSuccess();
2086 }
2087 else if (path.size() == 4 &&
2088 boost::ends_with(path[3], ".dcm"))
2089 {
2090 std::string sopInstanceUid = path[3];
2091 sopInstanceUid.resize(sopInstanceUid.size() - 4);
2092
2093 DatabaseLookup query;
2094 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1],
2095 true /* case sensitive */, true /* mandatory tag */);
2096 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2],
2097 true /* case sensitive */, true /* mandatory tag */);
2098 query.AddRestConstraint(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid,
2099 true /* case sensitive */, true /* mandatory tag */);
2100
2101 DicomFileVisitor visitor(context_, content, modificationTime);
2102 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */);
2103
2104 mime = MimeType_Dicom;
2105 return visitor.IsSuccess();
2106 }
2107 else
2108 {
2109 return false;
2110 }
2111 }
2112 else if (path[0] == BY_PATIENTS)
2113 {
2114 return patients_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
2115 }
2116 else if (path[0] == BY_STUDIES)
2117 {
2118 return studies_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
2119 }
2120 else if (path[0] == UPLOADS)
2121 {
2122 return uploads_.GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end()));
2123 }
2124 else
2125 {
2126 return false;
2127 }
2128 }
2129
2130
2131 virtual bool StoreFile(const std::string& content,
2132 const UriComponents& path) ORTHANC_OVERRIDE
2133 {
2134 if (path.size() >= 1 &&
2135 path[0] == UPLOADS)
2136 {
2137 UriComponents subpath(UriComponents(path.begin() + 1, path.end()));
2138
2139 if (uploads_.StoreFile(content, subpath))
2140 {
2141 if (!content.empty())
2142 {
2143 uploadQueue_.Enqueue(new SingleValueObject<std::string>(Toolbox::FlattenUri(subpath)));
2144 }
2145 return true;
2146 }
2147 else
2148 {
2149 return false;
2150 }
2151 }
2152 else
2153 {
2154 return false;
2155 }
2156 }
2157
2158
2159 virtual bool CreateFolder(const UriComponents& path)
2160 {
2161 if (path.size() >= 1 &&
2162 path[0] == UPLOADS)
2163 {
2164 return uploads_.CreateFolder(UriComponents(path.begin() + 1, path.end()));
2165 }
2166 else
2167 {
2168 return false;
2169 }
2170 }
2171
2172 virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE
2173 {
2174 if (path.size() >= 1 &&
2175 path[0] == UPLOADS)
2176 {
2177 return uploads_.DeleteItem(UriComponents(path.begin() + 1, path.end()));
2178 }
2179 else
2180 {
2181 return false; // read-only
2182 }
2183 }
2184
2185 virtual void Start() ORTHANC_OVERRIDE
2186 {
2187 if (running_)
2188 {
2189 throw OrthancException(ErrorCode_BadSequenceOfCalls);
2190 }
2191 else
2192 {
2193 LOG(INFO) << "Starting the WebDAV upload thread";
2194 running_ = true;
2195 uploadThread_ = boost::thread(UploadWorker, this);
2196 }
2197 }
2198
2199 virtual void Stop() ORTHANC_OVERRIDE
2200 {
2201 if (running_)
2202 {
2203 LOG(INFO) << "Stopping the WebDAV upload thread";
2204 running_ = false;
2205 if (uploadThread_.joinable())
2206 {
2207 uploadThread_.join();
2208 }
2209 }
2210 }
2211 };
2212
2213 613
2214 614
2215 static void PrintHelp(const char* path) 615 static void PrintHelp(const char* path)
2216 { 616 {
2217 std::cout 617 std::cout
2651 1051
2652 { 1052 {
2653 UriComponents root; // TODO 1053 UriComponents root; // TODO
2654 root.push_back("a"); 1054 root.push_back("a");
2655 root.push_back("b"); 1055 root.push_back("b");
2656 //httpServer.Register(root, new WebDavStorage(true)); 1056 httpServer.Register(root, new OrthancWebDav(context));
2657 //httpServer.Register(root, new DummyBucket(context, true));
2658 httpServer.Register(root, new DummyBucket2(context));
2659 } 1057 }
2660 1058
2661 if (httpServer.GetPortNumber() < 1024) 1059 if (httpServer.GetPortNumber() < 1024)
2662 { 1060 {
2663 LOG(WARNING) << "The HTTP port is privileged (" 1061 LOG(WARNING) << "The HTTP port is privileged ("