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 }