Mercurial > hg > orthanc
comparison OrthancServer/Sources/main.cpp @ 4232:688435755466
added DELETE in WebDAV, first working virtual filesystem
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 07 Oct 2020 13:00:57 +0200 |
parents | 290ffcb0a147 |
children | ca2a55a62c81 |
comparison
equal
deleted
inserted
replaced
4231:290ffcb0a147 | 4232:688435755466 |
---|---|
741 return false; | 741 return false; |
742 } | 742 } |
743 } | 743 } |
744 | 744 |
745 | 745 |
746 virtual bool CreateFolder(const UriComponents& path) | 746 virtual bool CreateFolder(const UriComponents& path) ORTHANC_OVERRIDE |
747 { | 747 { |
748 if (IsUploadedFolder(path)) | 748 if (IsUploadedFolder(path)) |
749 { | 749 { |
750 return storage_.CreateFolder(UriComponents(path.begin() + 1, path.end())); | 750 return storage_.CreateFolder(UriComponents(path.begin() + 1, path.end())); |
751 } | 751 } |
754 LOG(WARNING) << "Writing to a read-only location in WebDAV: " << Toolbox::FlattenUri(path); | 754 LOG(WARNING) << "Writing to a read-only location in WebDAV: " << Toolbox::FlattenUri(path); |
755 return false; | 755 return false; |
756 } | 756 } |
757 } | 757 } |
758 | 758 |
759 virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE | |
760 { | |
761 return false; // read-only | |
762 } | |
763 | |
759 virtual void Start() ORTHANC_OVERRIDE | 764 virtual void Start() ORTHANC_OVERRIDE |
760 { | 765 { |
761 LOG(WARNING) << "Starting WebDAV"; | 766 LOG(WARNING) << "Starting WebDAV"; |
762 } | 767 } |
763 | 768 |
769 | 774 |
770 | 775 |
771 | 776 |
772 | 777 |
773 | 778 |
774 static const char* const DICOM_IDENTIFIERS = "DicomIdentifiers"; | 779 static const char* const BY_UIDS = "by-uids"; |
775 | 780 |
776 class DummyBucket2 : public IWebDavBucket // TODO | 781 class DummyBucket2 : public IWebDavBucket // TODO |
777 { | 782 { |
778 private: | 783 private: |
779 ServerContext& context_; | 784 ServerContext& context_; |
780 | 785 |
786 | |
787 static void LookupTime(boost::posix_time::ptime& target, | |
788 ServerContext& context, | |
789 const std::string& publicId, | |
790 MetadataType metadata) | |
791 { | |
792 std::string value; | |
793 if (context.GetIndex().LookupMetadata(value, publicId, metadata)) | |
794 { | |
795 try | |
796 { | |
797 target = boost::posix_time::from_iso_string(value); | |
798 return; | |
799 } | |
800 catch (std::exception& e) | |
801 { | |
802 } | |
803 } | |
804 | |
805 target = boost::posix_time::second_clock::universal_time(); // Now | |
806 } | |
807 | |
808 | |
781 class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor | 809 class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor |
782 { | 810 { |
783 private: | 811 private: |
784 ServerContext& context_; | 812 ServerContext& context_; |
785 bool isComplete_; | 813 bool isComplete_; |
811 const std::string& instanceId /* unused */, | 839 const std::string& instanceId /* unused */, |
812 const DicomMap& mainDicomTags, | 840 const DicomMap& mainDicomTags, |
813 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | 841 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE |
814 { | 842 { |
815 DicomTag tag(0, 0); | 843 DicomTag tag(0, 0); |
844 MetadataType dateMetadata; | |
845 | |
816 switch (level_) | 846 switch (level_) |
817 { | 847 { |
818 case ResourceType_Study: | 848 case ResourceType_Study: |
819 tag = DICOM_TAG_STUDY_INSTANCE_UID; | 849 tag = DICOM_TAG_STUDY_INSTANCE_UID; |
850 dateMetadata = MetadataType_LastUpdate; | |
820 break; | 851 break; |
821 | 852 |
822 case ResourceType_Series: | 853 case ResourceType_Series: |
823 tag = DICOM_TAG_SERIES_INSTANCE_UID; | 854 tag = DICOM_TAG_SERIES_INSTANCE_UID; |
855 dateMetadata = MetadataType_LastUpdate; | |
824 break; | 856 break; |
825 | 857 |
826 case ResourceType_Instance: | 858 case ResourceType_Instance: |
827 tag = DICOM_TAG_SOP_INSTANCE_UID; | 859 tag = DICOM_TAG_SOP_INSTANCE_UID; |
860 dateMetadata = MetadataType_Instance_ReceptionDate; | |
828 break; | 861 break; |
829 | 862 |
830 default: | 863 default: |
831 throw OrthancException(ErrorCode_InternalError); | 864 throw OrthancException(ErrorCode_InternalError); |
832 } | 865 } |
833 | 866 |
834 std::string s; | 867 std::string s; |
835 if (mainDicomTags.LookupStringValue(s, tag, false) && | 868 if (mainDicomTags.LookupStringValue(s, tag, false) && |
836 !s.empty()) | 869 !s.empty()) |
837 { | 870 { |
871 std::unique_ptr<Resource> resource; | |
872 | |
838 if (level_ == ResourceType_Instance) | 873 if (level_ == ResourceType_Instance) |
839 { | 874 { |
840 FileInfo info; | 875 FileInfo info; |
841 if (context_.GetIndex().LookupAttachment(info, publicId, FileContentType_Dicom)) | 876 if (context_.GetIndex().LookupAttachment(info, publicId, FileContentType_Dicom)) |
842 { | 877 { |
843 std::unique_ptr<File> f(new File(s + ".dcm")); | 878 std::unique_ptr<File> f(new File(s + ".dcm")); |
844 f->SetMimeType(MimeType_Dicom); | 879 f->SetMimeType(MimeType_Dicom); |
845 f->SetContentLength(info.GetUncompressedSize()); | 880 f->SetContentLength(info.GetUncompressedSize()); |
846 target_.AddResource(f.release()); | 881 resource.reset(f.release()); |
847 } | 882 } |
848 } | 883 } |
849 else | 884 else |
850 { | 885 { |
851 target_.AddResource(new Folder(s)); | 886 resource.reset(new Folder(s)); |
852 } | 887 } |
888 | |
889 boost::posix_time::ptime t; | |
890 LookupTime(t, context_, publicId, dateMetadata); | |
891 resource->SetCreationTime(t); | |
892 | |
893 target_.AddResource(resource.release()); | |
853 } | 894 } |
854 } | 895 } |
855 }; | 896 }; |
856 | 897 |
857 class DicomFileVisitor : public ServerContext::ILookupVisitor | 898 class DicomFileVisitor : public ServerContext::ILookupVisitor |
858 { | 899 { |
859 private: | 900 private: |
860 ServerContext& context_; | 901 ServerContext& context_; |
861 bool success_; | 902 bool success_; |
862 std::string& target_; | 903 std::string& target_; |
904 boost::posix_time::ptime& modificationTime_; | |
863 | 905 |
864 public: | 906 public: |
865 DicomFileVisitor(ServerContext& context, | 907 DicomFileVisitor(ServerContext& context, |
866 std::string& target) : | 908 std::string& target, |
909 boost::posix_time::ptime& modificationTime) : | |
867 context_(context), | 910 context_(context), |
868 success_(false), | 911 success_(false), |
869 target_(target) | 912 target_(target), |
913 modificationTime_(modificationTime) | |
870 { | 914 { |
871 } | 915 } |
872 | 916 |
873 bool IsSuccess() const | 917 bool IsSuccess() const |
874 { | 918 { |
875 return success_; | 919 return success_; |
876 } | 920 } |
877 | 921 |
878 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | 922 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE |
879 { | 923 { |
880 return false; // (*) | 924 return false; // (*) |
881 } | 925 } |
882 | 926 |
887 virtual void Visit(const std::string& publicId, | 931 virtual void Visit(const std::string& publicId, |
888 const std::string& instanceId /* unused */, | 932 const std::string& instanceId /* unused */, |
889 const DicomMap& mainDicomTags, | 933 const DicomMap& mainDicomTags, |
890 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | 934 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE |
891 { | 935 { |
892 context_.ReadDicom(target_, publicId); | 936 if (success_) |
893 success_ = true; | 937 { |
938 success_ = false; // Two matches => Error | |
939 } | |
940 else | |
941 { | |
942 LookupTime(modificationTime_, context_, publicId, MetadataType_Instance_ReceptionDate); | |
943 context_.ReadDicom(target_, publicId); | |
944 success_ = true; | |
945 } | |
894 } | 946 } |
895 }; | 947 }; |
948 | |
949 class OrthancJsonVisitor : public ServerContext::ILookupVisitor | |
950 { | |
951 private: | |
952 ServerContext& context_; | |
953 bool success_; | |
954 std::string& target_; | |
955 ResourceType level_; | |
956 | |
957 public: | |
958 OrthancJsonVisitor(ServerContext& context, | |
959 std::string& target, | |
960 ResourceType level) : | |
961 context_(context), | |
962 success_(false), | |
963 target_(target), | |
964 level_(level) | |
965 { | |
966 } | |
967 | |
968 bool IsSuccess() const | |
969 { | |
970 return success_; | |
971 } | |
972 | |
973 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | |
974 { | |
975 return false; // (*) | |
976 } | |
977 | |
978 virtual void MarkAsComplete() ORTHANC_OVERRIDE | |
979 { | |
980 } | |
981 | |
982 virtual void Visit(const std::string& publicId, | |
983 const std::string& instanceId /* unused */, | |
984 const DicomMap& mainDicomTags, | |
985 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | |
986 { | |
987 Json::Value info; | |
988 if (context_.GetIndex().LookupResource(info, publicId, level_)) | |
989 { | |
990 if (success_) | |
991 { | |
992 success_ = false; // Two matches => Error | |
993 } | |
994 else | |
995 { | |
996 target_ = info.toStyledString(); | |
997 | |
998 // Replace UNIX newlines with DOS newlines | |
999 boost::replace_all(target_, "\n", "\r\n"); | |
1000 | |
1001 success_ = true; | |
1002 } | |
1003 } | |
1004 } | |
1005 }; | |
1006 | |
1007 | |
1008 void AddVirtualFile(Collection& collection, | |
1009 const UriComponents& path, | |
1010 const std::string& filename) | |
1011 { | |
1012 MimeType mime; | |
1013 std::string content; | |
1014 boost::posix_time::ptime modification; | |
1015 | |
1016 UriComponents p = path; | |
1017 p.push_back(filename); | |
1018 | |
1019 if (GetFileContent(mime, content, modification, p)) | |
1020 { | |
1021 std::unique_ptr<File> f(new File(filename)); | |
1022 f->SetMimeType(mime); | |
1023 f->SetContentLength(content.size()); | |
1024 f->SetCreationTime(modification); | |
1025 collection.AddResource(f.release()); | |
1026 } | |
1027 } | |
896 | 1028 |
897 public: | 1029 public: |
898 DummyBucket2(ServerContext& context) : | 1030 DummyBucket2(ServerContext& context) : |
899 context_(context) | 1031 context_(context) |
900 { | 1032 { |
904 { | 1036 { |
905 if (path.empty()) | 1037 if (path.empty()) |
906 { | 1038 { |
907 return true; | 1039 return true; |
908 } | 1040 } |
909 else if (path.front() == DICOM_IDENTIFIERS && | 1041 else if (path.front() == BY_UIDS) |
910 path.size() <= 3) | 1042 { |
911 { | 1043 return (path.size() <= 3 && |
912 return true; | 1044 (path.size() != 3 || path[2] != "study.json")); |
913 } | 1045 } |
914 else | 1046 else |
915 { | 1047 { |
916 return false; | 1048 return false; |
917 } | 1049 } |
920 virtual bool ListCollection(Collection& collection, | 1052 virtual bool ListCollection(Collection& collection, |
921 const UriComponents& path) ORTHANC_OVERRIDE | 1053 const UriComponents& path) ORTHANC_OVERRIDE |
922 { | 1054 { |
923 if (path.empty()) | 1055 if (path.empty()) |
924 { | 1056 { |
925 collection.AddResource(new Folder(DICOM_IDENTIFIERS)); | 1057 collection.AddResource(new Folder(BY_UIDS)); |
926 return true; | 1058 return true; |
927 } | 1059 } |
928 else if (path.front() == DICOM_IDENTIFIERS) | 1060 else if (path.front() == BY_UIDS) |
929 { | 1061 { |
930 DatabaseLookup query; | 1062 DatabaseLookup query; |
931 ResourceType level; | 1063 ResourceType level; |
1064 size_t limit = 0; // By default, no limits | |
932 | 1065 |
933 if (path.size() == 1) | 1066 if (path.size() == 1) |
934 { | 1067 { |
935 level = ResourceType_Study; | 1068 level = ResourceType_Study; |
1069 limit = 100; // TODO | |
936 } | 1070 } |
937 else if (path.size() == 2) | 1071 else if (path.size() == 2) |
938 { | 1072 { |
1073 AddVirtualFile(collection, path, "study.json"); | |
1074 | |
939 level = ResourceType_Series; | 1075 level = ResourceType_Series; |
940 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | 1076 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], |
941 true /* case sensitive */, true /* mandatory tag */); | 1077 true /* case sensitive */, true /* mandatory tag */); |
942 } | 1078 } |
943 else if (path.size() == 3) | 1079 else if (path.size() == 3) |
944 { | 1080 { |
1081 AddVirtualFile(collection, path, "series.json"); | |
1082 | |
945 level = ResourceType_Instance; | 1083 level = ResourceType_Instance; |
946 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | 1084 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], |
947 true /* case sensitive */, true /* mandatory tag */); | 1085 true /* case sensitive */, true /* mandatory tag */); |
948 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], | 1086 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], |
949 true /* case sensitive */, true /* mandatory tag */); | 1087 true /* case sensitive */, true /* mandatory tag */); |
952 { | 1090 { |
953 return false; | 1091 return false; |
954 } | 1092 } |
955 | 1093 |
956 DicomIdentifiersVisitor visitor(context_, collection, level); | 1094 DicomIdentifiersVisitor visitor(context_, collection, level); |
957 context_.Apply(visitor, query, level, 0 /* since */, 100 /* limit */); | 1095 context_.Apply(visitor, query, level, 0 /* since */, limit); |
958 | 1096 |
959 return true; | 1097 return true; |
960 } | 1098 } |
961 else | 1099 else |
962 { | 1100 { |
967 virtual bool GetFileContent(MimeType& mime, | 1105 virtual bool GetFileContent(MimeType& mime, |
968 std::string& content, | 1106 std::string& content, |
969 boost::posix_time::ptime& modificationTime, | 1107 boost::posix_time::ptime& modificationTime, |
970 const UriComponents& path) ORTHANC_OVERRIDE | 1108 const UriComponents& path) ORTHANC_OVERRIDE |
971 { | 1109 { |
972 if (path.size() == 4 && | 1110 if (!path.empty() && |
973 path[0] == DICOM_IDENTIFIERS && | 1111 path[0] == BY_UIDS) |
974 boost::ends_with(path[3], ".dcm")) | 1112 { |
975 { | 1113 if (path.size() == 3 && |
976 std::string sopInstanceUid = path[3]; | 1114 path[2] == "study.json") |
977 sopInstanceUid.resize(sopInstanceUid.size() - 4); | 1115 { |
1116 DatabaseLookup query; | |
1117 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1118 true /* case sensitive */, true /* mandatory tag */); | |
978 | 1119 |
979 mime = MimeType_Dicom; | 1120 OrthancJsonVisitor visitor(context_, content, ResourceType_Study); |
980 | 1121 context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */); |
981 DatabaseLookup query; | 1122 |
982 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | 1123 mime = MimeType_Json; |
1124 return visitor.IsSuccess(); | |
1125 } | |
1126 else if (path.size() == 4 && | |
1127 path[3] == "series.json") | |
1128 { | |
1129 DatabaseLookup query; | |
1130 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
983 true /* case sensitive */, true /* mandatory tag */); | 1131 true /* case sensitive */, true /* mandatory tag */); |
984 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], | 1132 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], |
985 true /* case sensitive */, true /* mandatory tag */); | 1133 true /* case sensitive */, true /* mandatory tag */); |
986 query.AddRestConstraint(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid, | |
987 true /* case sensitive */, true /* mandatory tag */); | |
988 | 1134 |
989 DicomFileVisitor visitor(context_, content); | 1135 OrthancJsonVisitor visitor(context_, content, ResourceType_Series); |
990 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 100 /* limit */); | 1136 context_.Apply(visitor, query, ResourceType_Series, 0 /* since */, 0 /* no limit */); |
991 | 1137 |
992 return visitor.IsSuccess(); | 1138 mime = MimeType_Json; |
993 } | 1139 return visitor.IsSuccess(); |
994 else | 1140 } |
995 { | 1141 else if (path.size() == 4 && |
996 return false; | 1142 boost::ends_with(path[3], ".dcm")) |
997 } | 1143 { |
1144 std::string sopInstanceUid = path[3]; | |
1145 sopInstanceUid.resize(sopInstanceUid.size() - 4); | |
1146 | |
1147 DatabaseLookup query; | |
1148 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1149 true /* case sensitive */, true /* mandatory tag */); | |
1150 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], | |
1151 true /* case sensitive */, true /* mandatory tag */); | |
1152 query.AddRestConstraint(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid, | |
1153 true /* case sensitive */, true /* mandatory tag */); | |
1154 | |
1155 DicomFileVisitor visitor(context_, content, modificationTime); | |
1156 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */); | |
1157 | |
1158 mime = MimeType_Dicom; | |
1159 return visitor.IsSuccess(); | |
1160 } | |
1161 } | |
1162 | |
1163 return false; | |
998 } | 1164 } |
999 | 1165 |
1000 | 1166 |
1001 virtual bool StoreFile(const std::string& content, | 1167 virtual bool StoreFile(const std::string& content, |
1002 const UriComponents& path) ORTHANC_OVERRIDE | 1168 const UriComponents& path) ORTHANC_OVERRIDE |
1006 | 1172 |
1007 | 1173 |
1008 virtual bool CreateFolder(const UriComponents& path) | 1174 virtual bool CreateFolder(const UriComponents& path) |
1009 { | 1175 { |
1010 return false; | 1176 return false; |
1177 } | |
1178 | |
1179 virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE | |
1180 { | |
1181 LOG(WARNING) << "DELETE: " << Toolbox::FlattenUri(path); | |
1182 return false; // read-only | |
1011 } | 1183 } |
1012 | 1184 |
1013 virtual void Start() ORTHANC_OVERRIDE | 1185 virtual void Start() ORTHANC_OVERRIDE |
1014 { | 1186 { |
1015 } | 1187 } |