4240
|
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 }
|