Mercurial > hg > orthanc
comparison OrthancServer/Sources/main.cpp @ 4239:c8754c4c1862
upload DICOM using WebDAV
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 09 Oct 2020 11:38:03 +0200 |
parents | c007fb7c8395 |
children | 799c0c527ced |
comparison
equal
deleted
inserted
replaced
4238:c007fb7c8395 | 4239:c8754c4c1862 |
---|---|
779 | 779 |
780 static const char* const BY_PATIENTS = "by-patients"; | 780 static const char* const BY_PATIENTS = "by-patients"; |
781 static const char* const BY_STUDIES = "by-studies"; | 781 static const char* const BY_STUDIES = "by-studies"; |
782 static const char* const BY_DATE = "by-dates"; | 782 static const char* const BY_DATE = "by-dates"; |
783 static const char* const BY_UIDS = "by-uids"; | 783 static const char* const BY_UIDS = "by-uids"; |
784 static const char* const UPLOADS = "uploads"; | |
784 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; | 785 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; |
785 | 786 |
786 class DummyBucket2 : public IWebDavBucket // TODO | 787 class DummyBucket2 : public IWebDavBucket // TODO |
787 { | 788 { |
788 private: | 789 private: |
789 typedef std::map<ResourceType, std::string> Templates; | 790 typedef std::map<ResourceType, std::string> Templates; |
790 | 791 |
791 | 792 |
793 static boost::posix_time::ptime GetNow() | |
794 { | |
795 return boost::posix_time::second_clock::universal_time(); | |
796 } | |
797 | |
798 | |
792 static void LookupTime(boost::posix_time::ptime& target, | 799 static void LookupTime(boost::posix_time::ptime& target, |
793 ServerContext& context, | 800 ServerContext& context, |
794 const std::string& publicId, | 801 const std::string& publicId, |
795 MetadataType metadata) | 802 MetadataType metadata) |
796 { | 803 { |
805 catch (std::exception& e) | 812 catch (std::exception& e) |
806 { | 813 { |
807 } | 814 } |
808 } | 815 } |
809 | 816 |
810 target = boost::posix_time::second_clock::universal_time(); // Now | 817 target = GetNow(); |
811 } | 818 } |
812 | 819 |
813 | 820 |
814 class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor | 821 class DicomIdentifiersVisitor : public ServerContext::ILookupVisitor |
815 { | 822 { |
1836 templates_(templates) | 1843 templates_(templates) |
1837 { | 1844 { |
1838 } | 1845 } |
1839 }; | 1846 }; |
1840 | 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 | |
1841 | 1904 |
1842 ServerContext& context_; | 1905 ServerContext& context_; |
1843 std::unique_ptr<INode> patients_; | 1906 std::unique_ptr<INode> patients_; |
1844 std::unique_ptr<INode> studies_; | 1907 std::unique_ptr<INode> studies_; |
1845 std::unique_ptr<INode> dates_; | 1908 std::unique_ptr<INode> dates_; |
1846 Templates patientsTemplates_; | 1909 Templates patientsTemplates_; |
1847 Templates studiesTemplates_; | 1910 Templates studiesTemplates_; |
1911 WebDavStorage uploads_; | |
1912 SharedMessageQueue uploadQueue_; | |
1913 boost::thread uploadThread_; | |
1914 bool running_; | |
1848 | 1915 |
1849 | |
1850 public: | 1916 public: |
1851 DummyBucket2(ServerContext& context) : | 1917 DummyBucket2(ServerContext& context) : |
1852 context_(context) | 1918 context_(context), |
1919 uploads_(false /* store uploads as temporary files */), | |
1920 running_(false) | |
1853 { | 1921 { |
1854 patientsTemplates_[ResourceType_Patient] = "{{PatientID}} - {{PatientName}}"; | 1922 patientsTemplates_[ResourceType_Patient] = "{{PatientID}} - {{PatientName}}"; |
1855 patientsTemplates_[ResourceType_Study] = "{{StudyDate}} - {{StudyDescription}}"; | 1923 patientsTemplates_[ResourceType_Study] = "{{StudyDate}} - {{StudyDescription}}"; |
1856 patientsTemplates_[ResourceType_Series] = "{{Modality}} - {{SeriesDescription}}"; | 1924 patientsTemplates_[ResourceType_Series] = "{{Modality}} - {{SeriesDescription}}"; |
1857 | 1925 |
1861 patients_.reset(new RootNode(context, ResourceType_Patient, patientsTemplates_)); | 1929 patients_.reset(new RootNode(context, ResourceType_Patient, patientsTemplates_)); |
1862 studies_.reset(new RootNode(context, ResourceType_Study, studiesTemplates_)); | 1930 studies_.reset(new RootNode(context, ResourceType_Study, studiesTemplates_)); |
1863 dates_.reset(new ListOfStudiesByYear(context, studiesTemplates_)); | 1931 dates_.reset(new ListOfStudiesByYear(context, studiesTemplates_)); |
1864 } | 1932 } |
1865 | 1933 |
1934 virtual ~DummyBucket2() | |
1935 { | |
1936 Stop(); | |
1937 } | |
1938 | |
1866 virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE | 1939 virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE |
1867 { | 1940 { |
1868 if (path.empty()) | 1941 if (path.empty()) |
1869 { | 1942 { |
1870 return true; | 1943 return true; |
1886 } | 1959 } |
1887 else if (path[0] == BY_DATE) | 1960 else if (path[0] == BY_DATE) |
1888 { | 1961 { |
1889 IWebDavBucket::Collection tmp; | 1962 IWebDavBucket::Collection tmp; |
1890 return dates_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end())); | 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())); | |
1891 } | 1968 } |
1892 else | 1969 else |
1893 { | 1970 { |
1894 return false; | 1971 return false; |
1895 } | 1972 } |
1902 { | 1979 { |
1903 collection.AddResource(new Folder(BY_DATE)); | 1980 collection.AddResource(new Folder(BY_DATE)); |
1904 collection.AddResource(new Folder(BY_PATIENTS)); | 1981 collection.AddResource(new Folder(BY_PATIENTS)); |
1905 collection.AddResource(new Folder(BY_STUDIES)); | 1982 collection.AddResource(new Folder(BY_STUDIES)); |
1906 collection.AddResource(new Folder(BY_UIDS)); | 1983 collection.AddResource(new Folder(BY_UIDS)); |
1984 collection.AddResource(new Folder(UPLOADS)); | |
1907 return true; | 1985 return true; |
1908 } | 1986 } |
1909 else if (path[0] == BY_UIDS) | 1987 else if (path[0] == BY_UIDS) |
1910 { | 1988 { |
1911 DatabaseLookup query; | 1989 DatabaseLookup query; |
1954 return studies_->ListCollection(collection, UriComponents(path.begin() + 1, path.end())); | 2032 return studies_->ListCollection(collection, UriComponents(path.begin() + 1, path.end())); |
1955 } | 2033 } |
1956 else if (path[0] == BY_DATE) | 2034 else if (path[0] == BY_DATE) |
1957 { | 2035 { |
1958 return dates_->ListCollection(collection, UriComponents(path.begin() + 1, path.end())); | 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())); | |
1959 } | 2041 } |
1960 else | 2042 else |
1961 { | 2043 { |
1962 return false; | 2044 return false; |
1963 } | 2045 } |
2020 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */); | 2102 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */); |
2021 | 2103 |
2022 mime = MimeType_Dicom; | 2104 mime = MimeType_Dicom; |
2023 return visitor.IsSuccess(); | 2105 return visitor.IsSuccess(); |
2024 } | 2106 } |
2107 else | |
2108 { | |
2109 return false; | |
2110 } | |
2025 } | 2111 } |
2026 else if (path[0] == BY_PATIENTS) | 2112 else if (path[0] == BY_PATIENTS) |
2027 { | 2113 { |
2028 return patients_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); | 2114 return patients_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); |
2029 } | 2115 } |
2030 else if (path[0] == BY_STUDIES) | 2116 else if (path[0] == BY_STUDIES) |
2031 { | 2117 { |
2032 return studies_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); | 2118 return studies_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); |
2033 } | 2119 } |
2034 else if (path[0] == BY_DATE) | 2120 else if (path[0] == UPLOADS) |
2035 { | 2121 { |
2036 return dates_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); | 2122 return uploads_.GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); |
2037 } | 2123 } |
2038 | 2124 else |
2039 return false; | 2125 { |
2126 return false; | |
2127 } | |
2040 } | 2128 } |
2041 | 2129 |
2042 | 2130 |
2043 virtual bool StoreFile(const std::string& content, | 2131 virtual bool StoreFile(const std::string& content, |
2044 const UriComponents& path) ORTHANC_OVERRIDE | 2132 const UriComponents& path) ORTHANC_OVERRIDE |
2045 { | 2133 { |
2046 return false; | 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 } | |
2047 } | 2156 } |
2048 | 2157 |
2049 | 2158 |
2050 virtual bool CreateFolder(const UriComponents& path) | 2159 virtual bool CreateFolder(const UriComponents& path) |
2051 { | 2160 { |
2052 return false; | 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 } | |
2053 } | 2170 } |
2054 | 2171 |
2055 virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE | 2172 virtual bool DeleteItem(const std::vector<std::string>& path) ORTHANC_OVERRIDE |
2056 { | 2173 { |
2057 LOG(WARNING) << "DELETE: " << Toolbox::FlattenUri(path); | 2174 if (path.size() >= 1 && |
2058 return false; // read-only | 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 } | |
2059 } | 2183 } |
2060 | 2184 |
2061 virtual void Start() ORTHANC_OVERRIDE | 2185 virtual void Start() ORTHANC_OVERRIDE |
2062 { | 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 } | |
2063 } | 2197 } |
2064 | 2198 |
2065 virtual void Stop() ORTHANC_OVERRIDE | 2199 virtual void Stop() ORTHANC_OVERRIDE |
2066 { | 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 } | |
2067 } | 2210 } |
2068 }; | 2211 }; |
2069 | 2212 |
2070 | 2213 |
2071 | 2214 |