Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancWebDav.cpp @ 4240:799c0c527ced
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 09 Oct 2020 12:02:40 +0200 |
parents | |
children | 3510da0e260c |
comparison
equal
deleted
inserted
replaced
4239:c8754c4c1862 | 4240:799c0c527ced |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "OrthancWebDav.h" | |
35 | |
36 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" | |
37 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" | |
38 #include "../../OrthancFramework/Sources/HttpServer/WebDavStorage.h" | |
39 #include "Search/DatabaseLookup.h" | |
40 #include "ServerContext.h" | |
41 | |
42 #include <boost/regex.hpp> | |
43 #include <boost/algorithm/string/predicate.hpp> | |
44 | |
45 | |
46 static const char* const BY_PATIENTS = "by-patients"; | |
47 static const char* const BY_STUDIES = "by-studies"; | |
48 static const char* const BY_DATE = "by-dates"; | |
49 static const char* const BY_UIDS = "by-uids"; | |
50 static const char* const UPLOADS = "uploads"; | |
51 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; | |
52 | |
53 | |
54 namespace Orthanc | |
55 { | |
56 static boost::posix_time::ptime GetNow() | |
57 { | |
58 return boost::posix_time::second_clock::universal_time(); | |
59 } | |
60 | |
61 | |
62 static void LookupTime(boost::posix_time::ptime& target, | |
63 ServerContext& context, | |
64 const std::string& publicId, | |
65 MetadataType metadata) | |
66 { | |
67 std::string value; | |
68 if (context.GetIndex().LookupMetadata(value, publicId, metadata)) | |
69 { | |
70 try | |
71 { | |
72 target = boost::posix_time::from_iso_string(value); | |
73 return; | |
74 } | |
75 catch (std::exception& e) | |
76 { | |
77 } | |
78 } | |
79 | |
80 target = GetNow(); | |
81 } | |
82 | |
83 | |
84 class OrthancWebDav::DicomIdentifiersVisitor : public ServerContext::ILookupVisitor | |
85 { | |
86 private: | |
87 ServerContext& context_; | |
88 bool isComplete_; | |
89 Collection& target_; | |
90 ResourceType level_; | |
91 | |
92 public: | |
93 DicomIdentifiersVisitor(ServerContext& context, | |
94 Collection& target, | |
95 ResourceType level) : | |
96 context_(context), | |
97 isComplete_(false), | |
98 target_(target), | |
99 level_(level) | |
100 { | |
101 } | |
102 | |
103 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | |
104 { | |
105 return false; // (*) | |
106 } | |
107 | |
108 virtual void MarkAsComplete() ORTHANC_OVERRIDE | |
109 { | |
110 isComplete_ = true; // TODO | |
111 } | |
112 | |
113 virtual void Visit(const std::string& publicId, | |
114 const std::string& instanceId /* unused */, | |
115 const DicomMap& mainDicomTags, | |
116 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | |
117 { | |
118 DicomTag tag(0, 0); | |
119 MetadataType timeMetadata; | |
120 | |
121 switch (level_) | |
122 { | |
123 case ResourceType_Study: | |
124 tag = DICOM_TAG_STUDY_INSTANCE_UID; | |
125 timeMetadata = MetadataType_LastUpdate; | |
126 break; | |
127 | |
128 case ResourceType_Series: | |
129 tag = DICOM_TAG_SERIES_INSTANCE_UID; | |
130 timeMetadata = MetadataType_LastUpdate; | |
131 break; | |
132 | |
133 case ResourceType_Instance: | |
134 tag = DICOM_TAG_SOP_INSTANCE_UID; | |
135 timeMetadata = MetadataType_Instance_ReceptionDate; | |
136 break; | |
137 | |
138 default: | |
139 throw OrthancException(ErrorCode_InternalError); | |
140 } | |
141 | |
142 std::string s; | |
143 if (mainDicomTags.LookupStringValue(s, tag, false) && | |
144 !s.empty()) | |
145 { | |
146 std::unique_ptr<Resource> resource; | |
147 | |
148 if (level_ == ResourceType_Instance) | |
149 { | |
150 FileInfo info; | |
151 if (context_.GetIndex().LookupAttachment(info, publicId, FileContentType_Dicom)) | |
152 { | |
153 std::unique_ptr<File> f(new File(s + ".dcm")); | |
154 f->SetMimeType(MimeType_Dicom); | |
155 f->SetContentLength(info.GetUncompressedSize()); | |
156 resource.reset(f.release()); | |
157 } | |
158 } | |
159 else | |
160 { | |
161 resource.reset(new Folder(s)); | |
162 } | |
163 | |
164 if (resource.get() != NULL) | |
165 { | |
166 boost::posix_time::ptime t; | |
167 LookupTime(t, context_, publicId, timeMetadata); | |
168 resource->SetCreationTime(t); | |
169 target_.AddResource(resource.release()); | |
170 } | |
171 } | |
172 } | |
173 }; | |
174 | |
175 | |
176 class OrthancWebDav::DicomFileVisitor : public ServerContext::ILookupVisitor | |
177 { | |
178 private: | |
179 ServerContext& context_; | |
180 bool success_; | |
181 std::string& target_; | |
182 boost::posix_time::ptime& time_; | |
183 | |
184 public: | |
185 DicomFileVisitor(ServerContext& context, | |
186 std::string& target, | |
187 boost::posix_time::ptime& time) : | |
188 context_(context), | |
189 success_(false), | |
190 target_(target), | |
191 time_(time) | |
192 { | |
193 } | |
194 | |
195 bool IsSuccess() const | |
196 { | |
197 return success_; | |
198 } | |
199 | |
200 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | |
201 { | |
202 return false; // (*) | |
203 } | |
204 | |
205 virtual void MarkAsComplete() ORTHANC_OVERRIDE | |
206 { | |
207 } | |
208 | |
209 virtual void Visit(const std::string& publicId, | |
210 const std::string& instanceId /* unused */, | |
211 const DicomMap& mainDicomTags, | |
212 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | |
213 { | |
214 if (success_) | |
215 { | |
216 success_ = false; // Two matches => Error | |
217 } | |
218 else | |
219 { | |
220 LookupTime(time_, context_, publicId, MetadataType_Instance_ReceptionDate); | |
221 context_.ReadDicom(target_, publicId); | |
222 success_ = true; | |
223 } | |
224 } | |
225 }; | |
226 | |
227 | |
228 class OrthancWebDav::OrthancJsonVisitor : public ServerContext::ILookupVisitor | |
229 { | |
230 private: | |
231 ServerContext& context_; | |
232 bool success_; | |
233 std::string& target_; | |
234 ResourceType level_; | |
235 | |
236 public: | |
237 OrthancJsonVisitor(ServerContext& context, | |
238 std::string& target, | |
239 ResourceType level) : | |
240 context_(context), | |
241 success_(false), | |
242 target_(target), | |
243 level_(level) | |
244 { | |
245 } | |
246 | |
247 bool IsSuccess() const | |
248 { | |
249 return success_; | |
250 } | |
251 | |
252 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | |
253 { | |
254 return false; // (*) | |
255 } | |
256 | |
257 virtual void MarkAsComplete() ORTHANC_OVERRIDE | |
258 { | |
259 } | |
260 | |
261 virtual void Visit(const std::string& publicId, | |
262 const std::string& instanceId /* unused */, | |
263 const DicomMap& mainDicomTags, | |
264 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | |
265 { | |
266 Json::Value info; | |
267 if (context_.GetIndex().LookupResource(info, publicId, level_)) | |
268 { | |
269 if (success_) | |
270 { | |
271 success_ = false; // Two matches => Error | |
272 } | |
273 else | |
274 { | |
275 target_ = info.toStyledString(); | |
276 | |
277 // Replace UNIX newlines with DOS newlines | |
278 boost::replace_all(target_, "\n", "\r\n"); | |
279 | |
280 success_ = true; | |
281 } | |
282 } | |
283 } | |
284 }; | |
285 | |
286 | |
287 class OrthancWebDav::ResourcesIndex : public boost::noncopyable | |
288 { | |
289 public: | |
290 typedef std::map<std::string, std::string> Map; | |
291 | |
292 private: | |
293 ServerContext& context_; | |
294 ResourceType level_; | |
295 std::string template_; | |
296 Map pathToResource_; | |
297 Map resourceToPath_; | |
298 | |
299 void CheckInvariants() | |
300 { | |
301 #ifndef NDEBUG | |
302 assert(pathToResource_.size() == resourceToPath_.size()); | |
303 | |
304 for (Map::const_iterator it = pathToResource_.begin(); it != pathToResource_.end(); ++it) | |
305 { | |
306 assert(resourceToPath_[it->second] == it->first); | |
307 } | |
308 | |
309 for (Map::const_iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it) | |
310 { | |
311 assert(pathToResource_[it->second] == it->first); | |
312 } | |
313 #endif | |
314 } | |
315 | |
316 void AddTags(DicomMap& target, | |
317 const std::string& resourceId, | |
318 ResourceType tagsFromLevel) | |
319 { | |
320 DicomMap tags; | |
321 if (context_.GetIndex().GetMainDicomTags(tags, resourceId, level_, tagsFromLevel)) | |
322 { | |
323 target.Merge(tags); | |
324 } | |
325 } | |
326 | |
327 void Register(const std::string& resourceId) | |
328 { | |
329 // Don't register twice the same resource | |
330 if (resourceToPath_.find(resourceId) == resourceToPath_.end()) | |
331 { | |
332 std::string name = template_; | |
333 | |
334 DicomMap tags; | |
335 | |
336 AddTags(tags, resourceId, level_); | |
337 | |
338 if (level_ == ResourceType_Study) | |
339 { | |
340 AddTags(tags, resourceId, ResourceType_Patient); | |
341 } | |
342 | |
343 DicomArray arr(tags); | |
344 for (size_t i = 0; i < arr.GetSize(); i++) | |
345 { | |
346 const DicomElement& element = arr.GetElement(i); | |
347 if (!element.GetValue().IsNull() && | |
348 !element.GetValue().IsBinary()) | |
349 { | |
350 const std::string tag = FromDcmtkBridge::GetTagName(element.GetTag(), ""); | |
351 boost::replace_all(name, "{{" + tag + "}}", element.GetValue().GetContent()); | |
352 } | |
353 } | |
354 | |
355 // Blank the tags that were not matched | |
356 static const boost::regex REGEX_BLANK_TAGS("{{.*?}}"); // non-greedy match | |
357 name = boost::regex_replace(name, REGEX_BLANK_TAGS, ""); | |
358 | |
359 // UTF-8 characters cannot be used on Windows XP | |
360 name = Toolbox::ConvertToAscii(name); | |
361 boost::replace_all(name, "/", ""); | |
362 boost::replace_all(name, "\\", ""); | |
363 | |
364 // Trim sequences of spaces as one single space | |
365 static const boost::regex REGEX_TRIM_SPACES("{{.*?}}"); | |
366 name = boost::regex_replace(name, REGEX_TRIM_SPACES, " "); | |
367 name = Toolbox::StripSpaces(name); | |
368 | |
369 size_t count = 0; | |
370 for (;;) | |
371 { | |
372 std::string path = name; | |
373 if (count > 0) | |
374 { | |
375 path += " (" + boost::lexical_cast<std::string>(count) + ")"; | |
376 } | |
377 | |
378 if (pathToResource_.find(path) == pathToResource_.end()) | |
379 { | |
380 pathToResource_[path] = resourceId; | |
381 resourceToPath_[resourceId] = path; | |
382 return; | |
383 } | |
384 | |
385 count++; | |
386 } | |
387 | |
388 throw OrthancException(ErrorCode_InternalError); | |
389 } | |
390 } | |
391 | |
392 public: | |
393 ResourcesIndex(ServerContext& context, | |
394 ResourceType level, | |
395 const std::string& templateString) : | |
396 context_(context), | |
397 level_(level), | |
398 template_(templateString) | |
399 { | |
400 } | |
401 | |
402 ResourceType GetLevel() const | |
403 { | |
404 return level_; | |
405 } | |
406 | |
407 void Refresh(std::set<std::string>& removedPaths /* out */, | |
408 const std::set<std::string>& resources) | |
409 { | |
410 CheckInvariants(); | |
411 | |
412 // Detect the resources that have been removed since last refresh | |
413 removedPaths.clear(); | |
414 std::set<std::string> removedResources; | |
415 | |
416 for (Map::iterator it = resourceToPath_.begin(); it != resourceToPath_.end(); ++it) | |
417 { | |
418 if (resources.find(it->first) == resources.end()) | |
419 { | |
420 const std::string& path = it->second; | |
421 | |
422 assert(pathToResource_.find(path) != pathToResource_.end()); | |
423 pathToResource_.erase(path); | |
424 removedPaths.insert(path); | |
425 | |
426 removedResources.insert(it->first); // Delay the removal to avoid disturbing the iterator | |
427 } | |
428 } | |
429 | |
430 // Remove the missing resources | |
431 for (std::set<std::string>::const_iterator it = removedResources.begin(); it != removedResources.end(); ++it) | |
432 { | |
433 assert(resourceToPath_.find(*it) != resourceToPath_.end()); | |
434 resourceToPath_.erase(*it); | |
435 } | |
436 | |
437 CheckInvariants(); | |
438 | |
439 for (std::set<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it) | |
440 { | |
441 Register(*it); | |
442 } | |
443 | |
444 CheckInvariants(); | |
445 } | |
446 | |
447 const Map& GetPathToResource() const | |
448 { | |
449 return pathToResource_; | |
450 } | |
451 }; | |
452 | |
453 | |
454 class OrthancWebDav::InstancesOfSeries : public INode | |
455 { | |
456 private: | |
457 ServerContext& context_; | |
458 std::string parentSeries_; | |
459 | |
460 public: | |
461 InstancesOfSeries(ServerContext& context, | |
462 const std::string& parentSeries) : | |
463 context_(context), | |
464 parentSeries_(parentSeries) | |
465 { | |
466 } | |
467 | |
468 virtual bool ListCollection(IWebDavBucket::Collection& target, | |
469 const UriComponents& path) ORTHANC_OVERRIDE | |
470 { | |
471 if (path.empty()) | |
472 { | |
473 std::list<std::string> resources; | |
474 try | |
475 { | |
476 context_.GetIndex().GetChildren(resources, parentSeries_); | |
477 } | |
478 catch (OrthancException&) | |
479 { | |
480 // Unknown (or deleted) parent series | |
481 return false; | |
482 } | |
483 | |
484 for (std::list<std::string>::const_iterator | |
485 it = resources.begin(); it != resources.end(); ++it) | |
486 { | |
487 boost::posix_time::ptime time; | |
488 LookupTime(time, context_, *it, MetadataType_Instance_ReceptionDate); | |
489 | |
490 FileInfo info; | |
491 if (context_.GetIndex().LookupAttachment(info, *it, FileContentType_Dicom)) | |
492 { | |
493 std::unique_ptr<File> resource(new File(*it + ".dcm")); | |
494 resource->SetMimeType(MimeType_Dicom); | |
495 resource->SetContentLength(info.GetUncompressedSize()); | |
496 resource->SetCreationTime(time); | |
497 target.AddResource(resource.release()); | |
498 } | |
499 } | |
500 | |
501 return true; | |
502 } | |
503 else | |
504 { | |
505 return false; | |
506 } | |
507 } | |
508 | |
509 virtual bool GetFileContent(MimeType& mime, | |
510 std::string& content, | |
511 boost::posix_time::ptime& time, | |
512 const UriComponents& path) ORTHANC_OVERRIDE | |
513 { | |
514 if (path.size() == 1 && | |
515 boost::ends_with(path[0], ".dcm")) | |
516 { | |
517 std::string instanceId = path[0].substr(0, path[0].size() - 4); | |
518 | |
519 try | |
520 { | |
521 mime = MimeType_Dicom; | |
522 context_.ReadDicom(content, instanceId); | |
523 LookupTime(time, context_, instanceId, MetadataType_Instance_ReceptionDate); | |
524 return true; | |
525 } | |
526 catch (OrthancException&) | |
527 { | |
528 // File was removed | |
529 return false; | |
530 } | |
531 } | |
532 else | |
533 { | |
534 return false; | |
535 } | |
536 } | |
537 }; | |
538 | |
539 | |
540 | |
541 /** | |
542 * The "InternalNode" class corresponds to a non-leaf node in the | |
543 * WebDAV tree, that only contains subfolders (no file). | |
544 * | |
545 * TODO: Implement a LRU index to dynamically remove the oldest | |
546 * children on high RAM usage. | |
547 **/ | |
548 class OrthancWebDav::InternalNode : public INode | |
549 { | |
550 private: | |
551 typedef std::map<std::string, INode*> Children; | |
552 | |
553 Children children_; | |
554 | |
555 INode* GetChild(const std::string& path) // Don't delete the result pointer! | |
556 { | |
557 Children::const_iterator child = children_.find(path); | |
558 if (child == children_.end()) | |
559 { | |
560 INode* child = CreateChild(path); | |
561 | |
562 if (child == NULL) | |
563 { | |
564 return NULL; | |
565 } | |
566 else | |
567 { | |
568 children_[path] = child; | |
569 return child; | |
570 } | |
571 } | |
572 else | |
573 { | |
574 assert(child->second != NULL); | |
575 return child->second; | |
576 } | |
577 } | |
578 | |
579 protected: | |
580 void RemoveSubfolder(const std::string& path) | |
581 { | |
582 Children::iterator child = children_.find(path); | |
583 if (child != children_.end()) | |
584 { | |
585 assert(child->second != NULL); | |
586 delete child->second; | |
587 children_.erase(child); | |
588 } | |
589 } | |
590 | |
591 virtual void Refresh() = 0; | |
592 | |
593 virtual bool ListSubfolders(IWebDavBucket::Collection& target) = 0; | |
594 | |
595 virtual INode* CreateChild(const std::string& path) = 0; | |
596 | |
597 public: | |
598 virtual ~InternalNode() | |
599 { | |
600 for (Children::iterator it = children_.begin(); it != children_.end(); ++it) | |
601 { | |
602 assert(it->second != NULL); | |
603 delete it->second; | |
604 } | |
605 } | |
606 | |
607 virtual bool ListCollection(IWebDavBucket::Collection& target, | |
608 const UriComponents& path) | |
609 ORTHANC_OVERRIDE ORTHANC_FINAL | |
610 { | |
611 Refresh(); | |
612 | |
613 if (path.empty()) | |
614 { | |
615 return ListSubfolders(target); | |
616 } | |
617 else | |
618 { | |
619 // Recursivity | |
620 INode* child = GetChild(path[0]); | |
621 if (child == NULL) | |
622 { | |
623 return false; | |
624 } | |
625 else | |
626 { | |
627 UriComponents subpath(path.begin() + 1, path.end()); | |
628 return child->ListCollection(target, subpath); | |
629 } | |
630 } | |
631 } | |
632 | |
633 virtual bool GetFileContent(MimeType& mime, | |
634 std::string& content, | |
635 boost::posix_time::ptime& time, | |
636 const UriComponents& path) | |
637 ORTHANC_OVERRIDE ORTHANC_FINAL | |
638 { | |
639 if (path.empty()) | |
640 { | |
641 return false; // An internal node doesn't correspond to a file | |
642 } | |
643 else | |
644 { | |
645 // Recursivity | |
646 Refresh(); | |
647 | |
648 INode* child = GetChild(path[0]); | |
649 if (child == NULL) | |
650 { | |
651 return false; | |
652 } | |
653 else | |
654 { | |
655 UriComponents subpath(path.begin() + 1, path.end()); | |
656 return child->GetFileContent(mime, content, time, subpath); | |
657 } | |
658 } | |
659 } | |
660 }; | |
661 | |
662 | |
663 class OrthancWebDav::ListOfResources : public InternalNode | |
664 { | |
665 private: | |
666 ServerContext& context_; | |
667 const Templates& templates_; | |
668 std::unique_ptr<ResourcesIndex> index_; | |
669 MetadataType timeMetadata_; | |
670 | |
671 protected: | |
672 virtual void Refresh() ORTHANC_OVERRIDE ORTHANC_FINAL | |
673 { | |
674 std::list<std::string> resources; | |
675 GetCurrentResources(resources); | |
676 | |
677 std::set<std::string> removedPaths; | |
678 index_->Refresh(removedPaths, std::set<std::string>(resources.begin(), resources.end())); | |
679 | |
680 // Remove the children whose associated resource doesn't exist anymore | |
681 for (std::set<std::string>::const_iterator | |
682 it = removedPaths.begin(); it != removedPaths.end(); ++it) | |
683 { | |
684 RemoveSubfolder(*it); | |
685 } | |
686 } | |
687 | |
688 virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE ORTHANC_FINAL | |
689 { | |
690 if (index_->GetLevel() == ResourceType_Instance) | |
691 { | |
692 // Not a collection, no subfolders | |
693 return false; | |
694 } | |
695 else | |
696 { | |
697 const ResourcesIndex::Map& paths = index_->GetPathToResource(); | |
698 | |
699 for (ResourcesIndex::Map::const_iterator it = paths.begin(); it != paths.end(); ++it) | |
700 { | |
701 boost::posix_time::ptime time; | |
702 LookupTime(time, context_, it->second, timeMetadata_); | |
703 | |
704 std::unique_ptr<IWebDavBucket::Resource> resource(new IWebDavBucket::Folder(it->first)); | |
705 resource->SetCreationTime(time); | |
706 target.AddResource(resource.release()); | |
707 } | |
708 | |
709 return true; | |
710 } | |
711 } | |
712 | |
713 virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE ORTHANC_FINAL | |
714 { | |
715 ResourcesIndex::Map::const_iterator resource = index_->GetPathToResource().find(path); | |
716 if (resource == index_->GetPathToResource().end()) | |
717 { | |
718 return NULL; | |
719 } | |
720 else | |
721 { | |
722 return CreateResourceNode(resource->second); | |
723 } | |
724 } | |
725 | |
726 ServerContext& GetContext() const | |
727 { | |
728 return context_; | |
729 } | |
730 | |
731 virtual void GetCurrentResources(std::list<std::string>& resources) = 0; | |
732 | |
733 virtual INode* CreateResourceNode(const std::string& resource) = 0; | |
734 | |
735 public: | |
736 ListOfResources(ServerContext& context, | |
737 ResourceType level, | |
738 const Templates& templates) : | |
739 context_(context), | |
740 templates_(templates) | |
741 { | |
742 Templates::const_iterator t = templates.find(level); | |
743 if (t == templates.end()) | |
744 { | |
745 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
746 } | |
747 | |
748 index_.reset(new ResourcesIndex(context, level, t->second)); | |
749 | |
750 if (level == ResourceType_Instance) | |
751 { | |
752 timeMetadata_ = MetadataType_Instance_ReceptionDate; | |
753 } | |
754 else | |
755 { | |
756 timeMetadata_ = MetadataType_LastUpdate; | |
757 } | |
758 } | |
759 | |
760 ResourceType GetLevel() const | |
761 { | |
762 return index_->GetLevel(); | |
763 } | |
764 | |
765 const Templates& GetTemplates() const | |
766 { | |
767 return templates_; | |
768 } | |
769 }; | |
770 | |
771 | |
772 | |
773 class OrthancWebDav::SingleDicomResource : public ListOfResources | |
774 { | |
775 private: | |
776 std::string parentId_; | |
777 | |
778 protected: | |
779 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE | |
780 { | |
781 try | |
782 { | |
783 GetContext().GetIndex().GetChildren(resources, parentId_); | |
784 } | |
785 catch (OrthancException&) | |
786 { | |
787 // Unknown parent resource | |
788 resources.clear(); | |
789 } | |
790 } | |
791 | |
792 virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE | |
793 { | |
794 if (GetLevel() == ResourceType_Instance) | |
795 { | |
796 return NULL; | |
797 } | |
798 else if (GetLevel() == ResourceType_Series) | |
799 { | |
800 return new InstancesOfSeries(GetContext(), resource); | |
801 } | |
802 else | |
803 { | |
804 ResourceType l = GetChildResourceType(GetLevel()); | |
805 return new SingleDicomResource(GetContext(), l, resource, GetTemplates()); | |
806 } | |
807 } | |
808 | |
809 public: | |
810 SingleDicomResource(ServerContext& context, | |
811 ResourceType level, | |
812 const std::string& parentId, | |
813 const Templates& templates) : | |
814 ListOfResources(context, level, templates), | |
815 parentId_(parentId) | |
816 { | |
817 } | |
818 }; | |
819 | |
820 | |
821 class OrthancWebDav::RootNode : public ListOfResources | |
822 { | |
823 protected: | |
824 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE | |
825 { | |
826 GetContext().GetIndex().GetAllUuids(resources, GetLevel()); | |
827 } | |
828 | |
829 virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE | |
830 { | |
831 if (GetLevel() == ResourceType_Series) | |
832 { | |
833 return new InstancesOfSeries(GetContext(), resource); | |
834 } | |
835 else | |
836 { | |
837 ResourceType l = GetChildResourceType(GetLevel()); | |
838 return new SingleDicomResource(GetContext(), l, resource, GetTemplates()); | |
839 } | |
840 } | |
841 | |
842 public: | |
843 RootNode(ServerContext& context, | |
844 ResourceType level, | |
845 const Templates& templates) : | |
846 ListOfResources(context, level, templates) | |
847 { | |
848 } | |
849 }; | |
850 | |
851 | |
852 class OrthancWebDav::ListOfStudiesByDate : public ListOfResources | |
853 { | |
854 private: | |
855 std::string year_; | |
856 std::string month_; | |
857 | |
858 class Visitor : public ServerContext::ILookupVisitor | |
859 { | |
860 private: | |
861 std::list<std::string>& resources_; | |
862 | |
863 public: | |
864 Visitor(std::list<std::string>& resources) : | |
865 resources_(resources) | |
866 { | |
867 } | |
868 | |
869 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | |
870 { | |
871 return false; // (*) | |
872 } | |
873 | |
874 virtual void MarkAsComplete() ORTHANC_OVERRIDE | |
875 { | |
876 } | |
877 | |
878 virtual void Visit(const std::string& publicId, | |
879 const std::string& instanceId /* unused */, | |
880 const DicomMap& mainDicomTags, | |
881 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | |
882 { | |
883 resources_.push_back(publicId); | |
884 } | |
885 }; | |
886 | |
887 protected: | |
888 virtual void GetCurrentResources(std::list<std::string>& resources) ORTHANC_OVERRIDE | |
889 { | |
890 DatabaseLookup query; | |
891 query.AddRestConstraint(DICOM_TAG_STUDY_DATE, year_ + month_ + "01-" + year_ + month_ + "31", | |
892 true /* case sensitive */, true /* mandatory tag */); | |
893 | |
894 Visitor visitor(resources); | |
895 GetContext().Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */); | |
896 } | |
897 | |
898 virtual INode* CreateResourceNode(const std::string& resource) ORTHANC_OVERRIDE | |
899 { | |
900 return new SingleDicomResource(GetContext(), ResourceType_Series, resource, GetTemplates()); | |
901 } | |
902 | |
903 public: | |
904 ListOfStudiesByDate(ServerContext& context, | |
905 const std::string& year, | |
906 const std::string& month, | |
907 const Templates& templates) : | |
908 ListOfResources(context, ResourceType_Study, templates), | |
909 year_(year), | |
910 month_(month) | |
911 { | |
912 if (year.size() != 4 || | |
913 month.size() != 2) | |
914 { | |
915 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
916 } | |
917 } | |
918 }; | |
919 | |
920 | |
921 class OrthancWebDav::ListOfStudiesByMonth : public InternalNode | |
922 { | |
923 private: | |
924 ServerContext& context_; | |
925 std::string year_; | |
926 const Templates& templates_; | |
927 | |
928 class Visitor : public ServerContext::ILookupVisitor | |
929 { | |
930 private: | |
931 std::set<std::string> months_; | |
932 | |
933 public: | |
934 Visitor() | |
935 { | |
936 } | |
937 | |
938 const std::set<std::string>& GetMonths() const | |
939 { | |
940 return months_; | |
941 } | |
942 | |
943 virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE | |
944 { | |
945 return false; // (*) | |
946 } | |
947 | |
948 virtual void MarkAsComplete() ORTHANC_OVERRIDE | |
949 { | |
950 } | |
951 | |
952 virtual void Visit(const std::string& publicId, | |
953 const std::string& instanceId /* unused */, | |
954 const DicomMap& mainDicomTags, | |
955 const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE | |
956 { | |
957 std::string s; | |
958 if (mainDicomTags.LookupStringValue(s, DICOM_TAG_STUDY_DATE, false) && | |
959 s.size() == 8) | |
960 { | |
961 months_.insert(s.substr(4, 2)); // Get the month from "YYYYMMDD" | |
962 } | |
963 } | |
964 }; | |
965 | |
966 protected: | |
967 virtual void Refresh() ORTHANC_OVERRIDE | |
968 { | |
969 } | |
970 | |
971 virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE | |
972 { | |
973 DatabaseLookup query; | |
974 query.AddRestConstraint(DICOM_TAG_STUDY_DATE, year_ + "0101-" + year_ + "1231", | |
975 true /* case sensitive */, true /* mandatory tag */); | |
976 | |
977 Visitor visitor; | |
978 context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */); | |
979 | |
980 for (std::set<std::string>::const_iterator it = visitor.GetMonths().begin(); | |
981 it != visitor.GetMonths().end(); ++it) | |
982 { | |
983 target.AddResource(new IWebDavBucket::Folder(year_ + "-" + *it)); | |
984 } | |
985 | |
986 return true; | |
987 } | |
988 | |
989 virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE | |
990 { | |
991 if (path.size() != 7) // Format: "YYYY-MM" | |
992 { | |
993 throw OrthancException(ErrorCode_InternalError); | |
994 } | |
995 else | |
996 { | |
997 const std::string year = path.substr(0, 4); | |
998 const std::string month = path.substr(5, 2); | |
999 return new ListOfStudiesByDate(context_, year, month, templates_); | |
1000 } | |
1001 } | |
1002 | |
1003 public: | |
1004 ListOfStudiesByMonth(ServerContext& context, | |
1005 const std::string& year, | |
1006 const Templates& templates) : | |
1007 context_(context), | |
1008 year_(year), | |
1009 templates_(templates) | |
1010 { | |
1011 if (year_.size() != 4) | |
1012 { | |
1013 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1014 } | |
1015 } | |
1016 }; | |
1017 | |
1018 | |
1019 class OrthancWebDav::ListOfStudiesByYear : public InternalNode | |
1020 { | |
1021 private: | |
1022 ServerContext& context_; | |
1023 const Templates& templates_; | |
1024 | |
1025 protected: | |
1026 virtual void Refresh() ORTHANC_OVERRIDE | |
1027 { | |
1028 } | |
1029 | |
1030 virtual bool ListSubfolders(IWebDavBucket::Collection& target) ORTHANC_OVERRIDE | |
1031 { | |
1032 std::list<std::string> resources; | |
1033 context_.GetIndex().GetAllUuids(resources, ResourceType_Study); | |
1034 | |
1035 std::set<std::string> years; | |
1036 | |
1037 for (std::list<std::string>::const_iterator it = resources.begin(); it != resources.end(); ++it) | |
1038 { | |
1039 DicomMap tags; | |
1040 std::string studyDate; | |
1041 if (context_.GetIndex().GetMainDicomTags(tags, *it, ResourceType_Study, ResourceType_Study) && | |
1042 tags.LookupStringValue(studyDate, DICOM_TAG_STUDY_DATE, false) && | |
1043 studyDate.size() == 8) | |
1044 { | |
1045 years.insert(studyDate.substr(0, 4)); // Get the year from "YYYYMMDD" | |
1046 } | |
1047 } | |
1048 | |
1049 for (std::set<std::string>::const_iterator it = years.begin(); it != years.end(); ++it) | |
1050 { | |
1051 target.AddResource(new IWebDavBucket::Folder(*it)); | |
1052 } | |
1053 | |
1054 return true; | |
1055 } | |
1056 | |
1057 virtual INode* CreateChild(const std::string& path) ORTHANC_OVERRIDE | |
1058 { | |
1059 return new ListOfStudiesByMonth(context_, path, templates_); | |
1060 } | |
1061 | |
1062 public: | |
1063 ListOfStudiesByYear(ServerContext& context, | |
1064 const Templates& templates) : | |
1065 context_(context), | |
1066 templates_(templates) | |
1067 { | |
1068 } | |
1069 }; | |
1070 | |
1071 | |
1072 void OrthancWebDav::AddVirtualFile(Collection& collection, | |
1073 const UriComponents& path, | |
1074 const std::string& filename) | |
1075 { | |
1076 MimeType mime; | |
1077 std::string content; | |
1078 boost::posix_time::ptime modification; | |
1079 | |
1080 UriComponents p = path; | |
1081 p.push_back(filename); | |
1082 | |
1083 if (GetFileContent(mime, content, modification, p)) | |
1084 { | |
1085 std::unique_ptr<File> f(new File(filename)); | |
1086 f->SetMimeType(mime); | |
1087 f->SetContentLength(content.size()); | |
1088 f->SetCreationTime(modification); | |
1089 collection.AddResource(f.release()); | |
1090 } | |
1091 } | |
1092 | |
1093 | |
1094 void OrthancWebDav::UploadWorker(OrthancWebDav* that) | |
1095 { | |
1096 assert(that != NULL); | |
1097 | |
1098 boost::posix_time::ptime lastModification = GetNow(); | |
1099 | |
1100 while (that->running_) | |
1101 { | |
1102 std::unique_ptr<IDynamicObject> obj(that->uploadQueue_.Dequeue(100)); | |
1103 if (obj.get() != NULL) | |
1104 { | |
1105 that->Upload(reinterpret_cast<const SingleValueObject<std::string>&>(*obj).GetValue()); | |
1106 lastModification = GetNow(); | |
1107 } | |
1108 else if (GetNow() - lastModification > boost::posix_time::seconds(10)) | |
1109 { | |
1110 // After every 10 seconds of inactivity, remove the empty folders | |
1111 LOG(INFO) << "Cleaning up the empty WebDAV upload folders"; | |
1112 that->uploads_.RemoveEmptyFolders(); | |
1113 lastModification = GetNow(); | |
1114 } | |
1115 } | |
1116 } | |
1117 | |
1118 | |
1119 void OrthancWebDav::Upload(const std::string& path) | |
1120 { | |
1121 UriComponents uri; | |
1122 Toolbox::SplitUriComponents(uri, path); | |
1123 | |
1124 LOG(INFO) << "Upload from WebDAV: " << path; | |
1125 | |
1126 MimeType mime; | |
1127 std::string content; | |
1128 boost::posix_time::ptime time; | |
1129 if (uploads_.GetFileContent(mime, content, time, uri)) | |
1130 { | |
1131 DicomInstanceToStore instance; | |
1132 // instance.SetOrigin(DicomInstanceOrigin_WebDav); | |
1133 instance.SetBuffer(content.c_str(), content.size()); | |
1134 | |
1135 std::string publicId; | |
1136 StoreStatus status = context_.Store(publicId, instance, StoreInstanceMode_Default); | |
1137 if (status == StoreStatus_Success || | |
1138 status == StoreStatus_AlreadyStored) | |
1139 { | |
1140 LOG(INFO) << "Successfully imported DICOM instance from WebDAV: " << path << " (Orthanc ID: " << publicId << ")"; | |
1141 uploads_.DeleteItem(uri); | |
1142 } | |
1143 else | |
1144 { | |
1145 LOG(WARNING) << "Cannot import DICOM instance from WebWAV: " << path; | |
1146 } | |
1147 } | |
1148 } | |
1149 | |
1150 | |
1151 OrthancWebDav::OrthancWebDav(ServerContext& context) : | |
1152 context_(context), | |
1153 uploads_(false /* store uploads as temporary files */), | |
1154 running_(false) | |
1155 { | |
1156 patientsTemplates_[ResourceType_Patient] = "{{PatientID}} - {{PatientName}}"; | |
1157 patientsTemplates_[ResourceType_Study] = "{{StudyDate}} - {{StudyDescription}}"; | |
1158 patientsTemplates_[ResourceType_Series] = "{{Modality}} - {{SeriesDescription}}"; | |
1159 | |
1160 studiesTemplates_[ResourceType_Study] = "{{PatientID}} - {{PatientName}} - {{StudyDescription}}"; | |
1161 studiesTemplates_[ResourceType_Series] = patientsTemplates_[ResourceType_Series]; | |
1162 | |
1163 patients_.reset(new RootNode(context, ResourceType_Patient, patientsTemplates_)); | |
1164 studies_.reset(new RootNode(context, ResourceType_Study, studiesTemplates_)); | |
1165 dates_.reset(new ListOfStudiesByYear(context, studiesTemplates_)); | |
1166 } | |
1167 | |
1168 | |
1169 bool OrthancWebDav::IsExistingFolder(const UriComponents& path) | |
1170 { | |
1171 if (path.empty()) | |
1172 { | |
1173 return true; | |
1174 } | |
1175 else if (path[0] == BY_UIDS) | |
1176 { | |
1177 return (path.size() <= 3 && | |
1178 (path.size() != 3 || path[2] != "study.json")); | |
1179 } | |
1180 else if (path[0] == BY_PATIENTS) | |
1181 { | |
1182 IWebDavBucket::Collection tmp; | |
1183 return patients_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end())); | |
1184 } | |
1185 else if (path[0] == BY_STUDIES) | |
1186 { | |
1187 IWebDavBucket::Collection tmp; | |
1188 return studies_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end())); | |
1189 } | |
1190 else if (path[0] == BY_DATE) | |
1191 { | |
1192 IWebDavBucket::Collection tmp; | |
1193 return dates_->ListCollection(tmp, UriComponents(path.begin() + 1, path.end())); | |
1194 } | |
1195 else if (path[0] == UPLOADS) | |
1196 { | |
1197 return uploads_.IsExistingFolder(UriComponents(path.begin() + 1, path.end())); | |
1198 } | |
1199 else | |
1200 { | |
1201 return false; | |
1202 } | |
1203 } | |
1204 | |
1205 | |
1206 bool OrthancWebDav::ListCollection(Collection& collection, | |
1207 const UriComponents& path) | |
1208 { | |
1209 if (path.empty()) | |
1210 { | |
1211 collection.AddResource(new Folder(BY_DATE)); | |
1212 collection.AddResource(new Folder(BY_PATIENTS)); | |
1213 collection.AddResource(new Folder(BY_STUDIES)); | |
1214 collection.AddResource(new Folder(BY_UIDS)); | |
1215 collection.AddResource(new Folder(UPLOADS)); | |
1216 return true; | |
1217 } | |
1218 else if (path[0] == BY_UIDS) | |
1219 { | |
1220 DatabaseLookup query; | |
1221 ResourceType level; | |
1222 size_t limit = 0; // By default, no limits | |
1223 | |
1224 if (path.size() == 1) | |
1225 { | |
1226 level = ResourceType_Study; | |
1227 limit = 100; // TODO | |
1228 } | |
1229 else if (path.size() == 2) | |
1230 { | |
1231 AddVirtualFile(collection, path, "study.json"); | |
1232 | |
1233 level = ResourceType_Series; | |
1234 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1235 true /* case sensitive */, true /* mandatory tag */); | |
1236 } | |
1237 else if (path.size() == 3) | |
1238 { | |
1239 AddVirtualFile(collection, path, "series.json"); | |
1240 | |
1241 level = ResourceType_Instance; | |
1242 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1243 true /* case sensitive */, true /* mandatory tag */); | |
1244 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], | |
1245 true /* case sensitive */, true /* mandatory tag */); | |
1246 } | |
1247 else | |
1248 { | |
1249 return false; | |
1250 } | |
1251 | |
1252 DicomIdentifiersVisitor visitor(context_, collection, level); | |
1253 context_.Apply(visitor, query, level, 0 /* since */, limit); | |
1254 | |
1255 return true; | |
1256 } | |
1257 else if (path[0] == BY_PATIENTS) | |
1258 { | |
1259 return patients_->ListCollection(collection, UriComponents(path.begin() + 1, path.end())); | |
1260 } | |
1261 else if (path[0] == BY_STUDIES) | |
1262 { | |
1263 return studies_->ListCollection(collection, UriComponents(path.begin() + 1, path.end())); | |
1264 } | |
1265 else if (path[0] == BY_DATE) | |
1266 { | |
1267 return dates_->ListCollection(collection, UriComponents(path.begin() + 1, path.end())); | |
1268 } | |
1269 else if (path[0] == UPLOADS) | |
1270 { | |
1271 return uploads_.ListCollection(collection, UriComponents(path.begin() + 1, path.end())); | |
1272 } | |
1273 else | |
1274 { | |
1275 return false; | |
1276 } | |
1277 } | |
1278 | |
1279 | |
1280 bool OrthancWebDav::GetFileContent(MimeType& mime, | |
1281 std::string& content, | |
1282 boost::posix_time::ptime& modificationTime, | |
1283 const UriComponents& path) | |
1284 { | |
1285 if (path.empty()) | |
1286 { | |
1287 return false; | |
1288 } | |
1289 else if (path[0] == BY_UIDS) | |
1290 { | |
1291 if (path.size() == 3 && | |
1292 path[2] == "study.json") | |
1293 { | |
1294 DatabaseLookup query; | |
1295 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1296 true /* case sensitive */, true /* mandatory tag */); | |
1297 | |
1298 OrthancJsonVisitor visitor(context_, content, ResourceType_Study); | |
1299 context_.Apply(visitor, query, ResourceType_Study, 0 /* since */, 0 /* no limit */); | |
1300 | |
1301 mime = MimeType_Json; | |
1302 return visitor.IsSuccess(); | |
1303 } | |
1304 else if (path.size() == 4 && | |
1305 path[3] == "series.json") | |
1306 { | |
1307 DatabaseLookup query; | |
1308 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1309 true /* case sensitive */, true /* mandatory tag */); | |
1310 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], | |
1311 true /* case sensitive */, true /* mandatory tag */); | |
1312 | |
1313 OrthancJsonVisitor visitor(context_, content, ResourceType_Series); | |
1314 context_.Apply(visitor, query, ResourceType_Series, 0 /* since */, 0 /* no limit */); | |
1315 | |
1316 mime = MimeType_Json; | |
1317 return visitor.IsSuccess(); | |
1318 } | |
1319 else if (path.size() == 4 && | |
1320 boost::ends_with(path[3], ".dcm")) | |
1321 { | |
1322 std::string sopInstanceUid = path[3]; | |
1323 sopInstanceUid.resize(sopInstanceUid.size() - 4); | |
1324 | |
1325 DatabaseLookup query; | |
1326 query.AddRestConstraint(DICOM_TAG_STUDY_INSTANCE_UID, path[1], | |
1327 true /* case sensitive */, true /* mandatory tag */); | |
1328 query.AddRestConstraint(DICOM_TAG_SERIES_INSTANCE_UID, path[2], | |
1329 true /* case sensitive */, true /* mandatory tag */); | |
1330 query.AddRestConstraint(DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid, | |
1331 true /* case sensitive */, true /* mandatory tag */); | |
1332 | |
1333 DicomFileVisitor visitor(context_, content, modificationTime); | |
1334 context_.Apply(visitor, query, ResourceType_Instance, 0 /* since */, 0 /* no limit */); | |
1335 | |
1336 mime = MimeType_Dicom; | |
1337 return visitor.IsSuccess(); | |
1338 } | |
1339 else | |
1340 { | |
1341 return false; | |
1342 } | |
1343 } | |
1344 else if (path[0] == BY_PATIENTS) | |
1345 { | |
1346 return patients_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); | |
1347 } | |
1348 else if (path[0] == BY_STUDIES) | |
1349 { | |
1350 return studies_->GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); | |
1351 } | |
1352 else if (path[0] == UPLOADS) | |
1353 { | |
1354 return uploads_.GetFileContent(mime, content, modificationTime, UriComponents(path.begin() + 1, path.end())); | |
1355 } | |
1356 else | |
1357 { | |
1358 return false; | |
1359 } | |
1360 } | |
1361 | |
1362 | |
1363 bool OrthancWebDav::StoreFile(const std::string& content, | |
1364 const UriComponents& path) | |
1365 { | |
1366 if (path.size() >= 1 && | |
1367 path[0] == UPLOADS) | |
1368 { | |
1369 UriComponents subpath(UriComponents(path.begin() + 1, path.end())); | |
1370 | |
1371 if (uploads_.StoreFile(content, subpath)) | |
1372 { | |
1373 if (!content.empty()) | |
1374 { | |
1375 uploadQueue_.Enqueue(new SingleValueObject<std::string>(Toolbox::FlattenUri(subpath))); | |
1376 } | |
1377 return true; | |
1378 } | |
1379 else | |
1380 { | |
1381 return false; | |
1382 } | |
1383 } | |
1384 else | |
1385 { | |
1386 return false; | |
1387 } | |
1388 } | |
1389 | |
1390 | |
1391 bool OrthancWebDav::CreateFolder(const UriComponents& path) | |
1392 { | |
1393 if (path.size() >= 1 && | |
1394 path[0] == UPLOADS) | |
1395 { | |
1396 return uploads_.CreateFolder(UriComponents(path.begin() + 1, path.end())); | |
1397 } | |
1398 else | |
1399 { | |
1400 return false; | |
1401 } | |
1402 } | |
1403 | |
1404 | |
1405 bool OrthancWebDav::DeleteItem(const std::vector<std::string>& path) | |
1406 { | |
1407 if (path.size() >= 1 && | |
1408 path[0] == UPLOADS) | |
1409 { | |
1410 return uploads_.DeleteItem(UriComponents(path.begin() + 1, path.end())); | |
1411 } | |
1412 else | |
1413 { | |
1414 return false; // read-only | |
1415 } | |
1416 } | |
1417 | |
1418 | |
1419 void OrthancWebDav::Start() | |
1420 { | |
1421 if (running_) | |
1422 { | |
1423 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
1424 } | |
1425 else | |
1426 { | |
1427 LOG(INFO) << "Starting the WebDAV upload thread"; | |
1428 running_ = true; | |
1429 uploadThread_ = boost::thread(UploadWorker, this); | |
1430 } | |
1431 } | |
1432 | |
1433 | |
1434 void OrthancWebDav::Stop() | |
1435 { | |
1436 if (running_) | |
1437 { | |
1438 LOG(INFO) << "Stopping the WebDAV upload thread"; | |
1439 running_ = false; | |
1440 if (uploadThread_.joinable()) | |
1441 { | |
1442 uploadThread_.join(); | |
1443 } | |
1444 } | |
1445 } | |
1446 } |