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