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