comparison OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp @ 1899:917500c46fe0

moved the Platform folder from the Applications folder to the Stone library itself
author Alain Mazy <am@osimis.io>
date Sat, 29 Jan 2022 12:47:32 +0100
parents
children 07964689cb0b
comparison
equal deleted inserted replaced
1898:a5e54bd87b25 1899:917500c46fe0
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 **/
22
23
24 #if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
25 # include "WebAssemblyOracle_Includes.h"
26 #else
27 // This is the case when using the WebAssembly side module, and this
28 // source file must be compiled within the WebAssembly main module
29 # include <Oracle/WebAssemblyOracle_Includes.h>
30 #endif
31
32 #include <OrthancException.h>
33 #include <Toolbox.h>
34
35 #include <emscripten.h>
36 #include <emscripten/html5.h>
37 #include <emscripten/fetch.h>
38
39
40 #if ORTHANC_ENABLE_DCMTK == 1
41 static unsigned int BUCKET_SOP = 1;
42 #endif
43
44
45 namespace OrthancStone
46 {
47 class WebAssemblyOracle::TimeoutContext
48 {
49 private:
50 WebAssemblyOracle& oracle_;
51 boost::weak_ptr<IObserver> receiver_;
52 std::unique_ptr<SleepOracleCommand> command_;
53
54 public:
55 TimeoutContext(WebAssemblyOracle& oracle,
56 boost::weak_ptr<IObserver> receiver,
57 IOracleCommand* command) :
58 oracle_(oracle),
59 receiver_(receiver)
60 {
61 if (command == NULL)
62 {
63 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
64 }
65 else
66 {
67 command_.reset(dynamic_cast<SleepOracleCommand*>(command));
68 }
69 }
70
71 void EmitMessage()
72 {
73 assert(command_.get() != NULL);
74
75 SleepOracleCommand::TimeoutMessage message(*command_);
76 oracle_.EmitMessage(receiver_, message);
77 }
78
79 static void Callback(void *userData)
80 {
81 std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData));
82 context->EmitMessage();
83 }
84 };
85
86
87 /**
88 This object is created on the heap for every http request.
89 It is deleted in the success (or error) callbacks.
90
91 This object references the receiver of the request. Since this is a raw
92 reference, we need additional checks to make sure we send the response to
93 the same object, for the object can be deleted and a new one recreated at the
94 same address (it often happens in the [single-threaded] browser context).
95 */
96 class WebAssemblyOracle::FetchContext : public boost::noncopyable
97 {
98 private:
99 WebAssemblyOracle& oracle_;
100 boost::weak_ptr<IObserver> receiver_;
101 std::unique_ptr<IOracleCommand> command_;
102 std::string expectedContentType_;
103
104 public:
105 FetchContext(WebAssemblyOracle& oracle,
106 boost::weak_ptr<IObserver> receiver,
107 IOracleCommand* command,
108 const std::string& expectedContentType) :
109 oracle_(oracle),
110 receiver_(receiver),
111 command_(command),
112 expectedContentType_(expectedContentType)
113 {
114 if (Orthanc::Logging::IsTraceLevelEnabled())
115 {
116 // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled
117 LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | "
118 << "receiver address = " << std::hex << receiver.lock().get();
119 }
120
121 if (command == NULL)
122 {
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
124 }
125 }
126
127 const std::string& GetExpectedContentType() const
128 {
129 return expectedContentType_;
130 }
131
132 void EmitException(const Orthanc::OrthancException& exception)
133 {
134 assert(command_.get() != NULL);
135 OracleCommandExceptionMessage message(*command_, exception);
136 oracle_.EmitMessage(receiver_, message);
137 }
138
139 void ProcessFetchResult(const std::string& answer,
140 const HttpHeaders& headers)
141 {
142 assert(command_.get() != NULL);
143 oracle_.ProcessFetchResult(receiver_, answer, headers, *command_);
144 }
145
146 static void SuccessCallback(emscripten_fetch_t *fetch)
147 {
148 /**
149 * Firstly, make a local copy of the fetched information, and
150 * free data associated with the fetch.
151 **/
152
153 if (fetch->userData == NULL)
154 {
155 LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!";
156 return;
157 }
158
159 std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
160
161 std::string answer;
162 if (fetch->numBytes > 0)
163 {
164 answer.assign(fetch->data, fetch->numBytes);
165 }
166
167
168 /**
169 * Retrieving the headers of the HTTP answer.
170 **/
171 HttpHeaders headers;
172
173 #if (__EMSCRIPTEN_major__ < 1 || \
174 (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \
175 (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37))
176 # warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API
177
178 /**
179 * HACK - If emscripten < 1.38.37, the fetch API does not
180 * contain a way to retrieve the HTTP headers of the answer. We
181 * make the assumption that the "Content-Type" header of the
182 * response is the same as the "Accept" header of the
183 * query. This is fixed thanks to the
184 * "emscripten_fetch_get_response_headers()" function that was
185 * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26.
186 *
187 * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h
188 * https://github.com/emscripten-core/emscripten/pull/8486
189 **/
190 if (fetch->userData != NULL)
191 {
192 if (!context->GetExpectedContentType().empty())
193 {
194 headers["Content-Type"] = context->GetExpectedContentType();
195 }
196 }
197 #else
198 {
199 size_t size = emscripten_fetch_get_response_headers_length(fetch);
200
201 std::string plainHeaders(size + 1, '\0');
202 emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1);
203
204 std::vector<std::string> tokens;
205 Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n');
206
207 for (size_t i = 0; i < tokens.size(); i++)
208 {
209 size_t p = tokens[i].find(':');
210 if (p != std::string::npos)
211 {
212 std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p));
213 std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1));
214 headers[key] = value;
215 }
216 }
217 }
218 #endif
219
220 LOG(TRACE) << "About to call emscripten_fetch_close";
221 emscripten_fetch_close(fetch);
222 LOG(TRACE) << "Successfully called emscripten_fetch_close";
223
224 /**
225 * Secondly, use the retrieved data.
226 * IMPORTANT NOTE: the receiver might be dead. This is prevented
227 * by the object responsible for zombie check, later on.
228 **/
229 try
230 {
231 if (context.get() == NULL)
232 {
233 LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)";
234 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
235 }
236 else
237 {
238 context->ProcessFetchResult(answer, headers);
239 }
240 }
241 catch (Orthanc::OrthancException& e)
242 {
243 LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What();
244 context->EmitException(e);
245 }
246 }
247
248 static void FailureCallback(emscripten_fetch_t *fetch)
249 {
250 std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
251
252 #if 0
253 {
254 const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText);
255 char message[kEmscriptenStatusTextSize + 1];
256 memcpy(message, fetch->statusText, kEmscriptenStatusTextSize);
257 message[kEmscriptenStatusTextSize] = 0;
258
259 LOG(ERROR) << "Fetching " << fetch->url
260 << " failed, HTTP failure status code: " << fetch->status
261 << " | statusText = " << message
262 << " | numBytes = " << fetch->numBytes
263 << " | totalBytes = " << fetch->totalBytes
264 << " | readyState = " << fetch->readyState;
265 }
266 #endif
267
268 context->EmitException(Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol));
269
270 /**
271 * TODO - The following code leads to an infinite recursion, at
272 * least with Firefox running on incognito mode => WHY?
273 **/
274 emscripten_fetch_close(fetch); // Also free data on failure.
275 }
276 };
277
278
279
280 class WebAssemblyOracle::FetchCommand : public boost::noncopyable
281 {
282 private:
283 WebAssemblyOracle& oracle_;
284 boost::weak_ptr<IObserver> receiver_;
285 std::unique_ptr<IOracleCommand> command_;
286 Orthanc::HttpMethod method_;
287 std::string url_;
288 std::string body_;
289 HttpHeaders headers_;
290 unsigned int timeout_;
291 std::string expectedContentType_;
292 bool hasCredentials_;
293 std::string username_;
294 std::string password_;
295
296 public:
297 FetchCommand(WebAssemblyOracle& oracle,
298 boost::weak_ptr<IObserver> receiver,
299 IOracleCommand* command) :
300 oracle_(oracle),
301 receiver_(receiver),
302 command_(command),
303 method_(Orthanc::HttpMethod_Get),
304 timeout_(0),
305 hasCredentials_(false)
306 {
307 if (command == NULL)
308 {
309 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
310 }
311 }
312
313 void SetMethod(Orthanc::HttpMethod method)
314 {
315 method_ = method;
316 }
317
318 Orthanc::HttpMethod GetMethod() const
319 {
320 return method_;
321 }
322
323 void SetUrl(const std::string& url)
324 {
325 url_ = url;
326 }
327
328 const std::string& GetUrl() const
329 {
330 return url_;
331 }
332
333 void SetBody(std::string& body /* will be swapped */)
334 {
335 body_.swap(body);
336 }
337
338 void AddHttpHeaders(const HttpHeaders& headers)
339 {
340 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
341 {
342 headers_[it->first] = it->second;
343 }
344 }
345
346 const HttpHeaders& GetHttpHeaders() const
347 {
348 return headers_;
349 }
350
351 void SetTimeout(unsigned int timeout)
352 {
353 timeout_ = timeout;
354 }
355
356 unsigned int GetTimeout() const
357 {
358 return timeout_;
359 }
360
361 void SetCredentials(const std::string& username,
362 const std::string& password)
363 {
364 hasCredentials_ = true;
365 username_ = username;
366 password_ = password;
367 }
368
369 void Execute()
370 {
371 if (command_.get() == NULL)
372 {
373 // Cannot call Execute() twice
374 LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)";
375 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
376 }
377
378 emscripten_fetch_attr_t attr;
379 emscripten_fetch_attr_init(&attr);
380
381 const char* method;
382
383 switch (method_)
384 {
385 case Orthanc::HttpMethod_Get:
386 method = "GET";
387 break;
388
389 case Orthanc::HttpMethod_Post:
390 method = "POST";
391 break;
392
393 case Orthanc::HttpMethod_Delete:
394 method = "DELETE";
395 break;
396
397 case Orthanc::HttpMethod_Put:
398 method = "PUT";
399 break;
400
401 default:
402 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
403 }
404
405 strcpy(attr.requestMethod, method);
406
407 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
408 attr.onsuccess = FetchContext::SuccessCallback;
409 attr.onerror = FetchContext::FailureCallback;
410 attr.timeoutMSecs = timeout_ * 1000;
411
412 if (hasCredentials_)
413 {
414 attr.withCredentials = EM_TRUE;
415 attr.userName = username_.c_str();
416 attr.password = password_.c_str();
417 }
418
419 std::vector<const char*> headers;
420 headers.reserve(2 * headers_.size() + 1);
421
422 std::string expectedContentType;
423
424 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
425 {
426 std::string key;
427 Orthanc::Toolbox::ToLowerCase(key, it->first);
428
429 if (key == "accept")
430 {
431 expectedContentType = it->second;
432 }
433
434 if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header
435 {
436 headers.push_back(it->first.c_str());
437 headers.push_back(it->second.c_str());
438 }
439 }
440
441 headers.push_back(NULL); // Termination of the array of HTTP headers
442
443 attr.requestHeaders = &headers[0];
444
445 char* requestData = NULL;
446 if (!body_.empty())
447 requestData = reinterpret_cast<char*>(malloc(body_.size()));
448
449 try
450 {
451 if (!body_.empty())
452 {
453 memcpy(requestData, &(body_[0]), body_.size());
454 attr.requestDataSize = body_.size();
455 attr.requestData = requestData;
456 }
457 attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType);
458
459 // Must be the last call to prevent memory leak on error
460 emscripten_fetch(&attr, url_.c_str());
461 }
462 catch(...)
463 {
464 if(requestData != NULL)
465 free(requestData);
466 throw;
467 }
468 }
469 };
470
471
472 void WebAssemblyOracle::ProcessFetchResult(boost::weak_ptr<IObserver>& receiver,
473 const std::string& answer,
474 const HttpHeaders& headers,
475 const IOracleCommand& command)
476 {
477 switch (command.GetType())
478 {
479 case IOracleCommand::Type_Http:
480 {
481 HttpCommand::SuccessMessage message(dynamic_cast<const HttpCommand&>(command), headers, answer);
482 EmitMessage(receiver, message);
483 break;
484 }
485
486 case IOracleCommand::Type_OrthancRestApi:
487 {
488 LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call EmitMessage(message);";
489 OrthancRestApiCommand::SuccessMessage message
490 (dynamic_cast<const OrthancRestApiCommand&>(command), headers, answer);
491 EmitMessage(receiver, message);
492 break;
493 }
494
495 case IOracleCommand::Type_GetOrthancImage:
496 {
497 dynamic_cast<const GetOrthancImageCommand&>(command).ProcessHttpAnswer(receiver, *this, answer, headers);
498 break;
499 }
500
501 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
502 {
503 dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command).ProcessHttpAnswer(receiver, *this, answer);
504 break;
505 }
506
507 case IOracleCommand::Type_ParseDicomFromWado:
508 {
509 #if ORTHANC_ENABLE_DCMTK == 1
510 const ParseDicomFromWadoCommand& c = dynamic_cast<const ParseDicomFromWadoCommand&>(command);
511
512 size_t fileSize;
513 std::unique_ptr<Orthanc::ParsedDicomFile> dicom
514 (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
515
516 {
517 ParseDicomSuccessMessage message(c, c.GetSource(), *dicom, fileSize, true);
518 EmitMessage(receiver, message);
519 }
520
521 if (dicomCache_.get())
522 {
523 // Store it into the cache for future use
524 dicomCache_->Acquire(BUCKET_SOP, c.GetSopInstanceUid(), dicom.release(), fileSize, true);
525 }
526 #else
527 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
528 #endif
529 break;
530 }
531
532 default:
533 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
534 << command.GetType();
535 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
536 }
537 }
538
539
540 void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command,
541 const std::string& uri) const
542 {
543 if (isLocalOrthanc_)
544 {
545 command.SetUrl(localOrthancRoot_ + uri);
546 }
547 else
548 {
549 command.SetUrl(remoteOrthanc_.GetUrl() + uri);
550 command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders());
551
552 if (!remoteOrthanc_.GetUsername().empty())
553 {
554 command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword());
555 }
556 }
557 }
558
559
560 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
561 HttpCommand* command)
562 {
563 FetchCommand fetch(*this, receiver, command);
564
565 fetch.SetMethod(command->GetMethod());
566 fetch.SetUrl(command->GetUrl());
567 fetch.AddHttpHeaders(command->GetHttpHeaders());
568 fetch.SetTimeout(command->GetTimeout());
569
570 if (command->GetMethod() == Orthanc::HttpMethod_Post ||
571 command->GetMethod() == Orthanc::HttpMethod_Put)
572 {
573 std::string body;
574 command->SwapBody(body);
575 fetch.SetBody(body);
576 }
577
578 fetch.Execute();
579 }
580
581
582 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
583 OrthancRestApiCommand* command)
584 {
585 try
586 {
587 //LOG(TRACE) << "*********** WebAssemblyOracle::Execute.";
588 //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command;
589 FetchCommand fetch(*this, receiver, command);
590
591 fetch.SetMethod(command->GetMethod());
592 SetOrthancUrl(fetch, command->GetUri());
593 fetch.AddHttpHeaders(command->GetHttpHeaders());
594 fetch.SetTimeout(command->GetTimeout());
595
596 if (command->GetMethod() == Orthanc::HttpMethod_Post ||
597 command->GetMethod() == Orthanc::HttpMethod_Put)
598 {
599 std::string body;
600 command->SwapBody(body);
601 fetch.SetBody(body);
602 }
603
604 fetch.Execute();
605 //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute.";
606 }
607 catch (const Orthanc::OrthancException& e)
608 {
609 if (e.HasDetails())
610 {
611 LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails();
612 }
613 else
614 {
615 LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What();
616 }
617 //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
618 throw;
619 }
620 catch (const std::exception& e)
621 {
622 LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what();
623 // LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
624 throw;
625 }
626 catch (...)
627 {
628 LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute";
629 // LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
630 throw;
631 }
632 }
633
634
635 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
636 GetOrthancImageCommand* command)
637 {
638 FetchCommand fetch(*this, receiver, command);
639
640 SetOrthancUrl(fetch, command->GetUri());
641 fetch.AddHttpHeaders(command->GetHttpHeaders());
642 fetch.SetTimeout(command->GetTimeout());
643
644 fetch.Execute();
645 }
646
647
648 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
649 GetOrthancWebViewerJpegCommand* command)
650 {
651 FetchCommand fetch(*this, receiver, command);
652
653 SetOrthancUrl(fetch, command->GetUri());
654 fetch.AddHttpHeaders(command->GetHttpHeaders());
655 fetch.SetTimeout(command->GetTimeout());
656
657 fetch.Execute();
658 }
659
660
661 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
662 ParseDicomFromWadoCommand* command)
663 {
664 std::unique_ptr<ParseDicomFromWadoCommand> protection(command);
665
666 #if ORTHANC_ENABLE_DCMTK == 1
667 if (dicomCache_.get())
668 {
669 ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid());
670 if (reader.IsValid() &&
671 reader.HasPixelData())
672 {
673 // Reuse the DICOM file from the cache
674 ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(),
675 reader.GetFileSize(), reader.HasPixelData());
676 EmitMessage(receiver, message);
677 return;
678 }
679 }
680 #endif
681
682 switch (command->GetRestCommand().GetType())
683 {
684 case IOracleCommand::Type_Http:
685 {
686 const HttpCommand& rest =
687 dynamic_cast<const HttpCommand&>(protection->GetRestCommand());
688
689 FetchCommand fetch(*this, receiver, protection.release());
690
691 fetch.SetMethod(rest.GetMethod());
692 fetch.SetUrl(rest.GetUrl());
693 fetch.AddHttpHeaders(rest.GetHttpHeaders());
694 fetch.SetTimeout(rest.GetTimeout());
695
696 if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
697 rest.GetMethod() == Orthanc::HttpMethod_Put)
698 {
699 std::string body = rest.GetBody();
700 fetch.SetBody(body);
701 }
702
703 fetch.Execute();
704 break;
705 }
706
707 case IOracleCommand::Type_OrthancRestApi:
708 {
709 const OrthancRestApiCommand& rest =
710 dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand());
711
712 FetchCommand fetch(*this, receiver, protection.release());
713
714 fetch.SetMethod(rest.GetMethod());
715 SetOrthancUrl(fetch, rest.GetUri());
716 fetch.AddHttpHeaders(rest.GetHttpHeaders());
717 fetch.SetTimeout(rest.GetTimeout());
718
719 if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
720 rest.GetMethod() == Orthanc::HttpMethod_Put)
721 {
722 std::string body = rest.GetBody();
723 fetch.SetBody(body);
724 }
725
726 fetch.Execute();
727 break;
728 }
729
730 default:
731 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
732 }
733 }
734
735
736 bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver,
737 IOracleCommand* command)
738 {
739 LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
740 << std::hex << receiver.get();
741
742 std::unique_ptr<IOracleCommand> protection(command);
743
744 if (command == NULL)
745 {
746 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
747 }
748
749 switch (command->GetType())
750 {
751 case IOracleCommand::Type_Http:
752 Execute(receiver, dynamic_cast<HttpCommand*>(protection.release()));
753 break;
754
755 case IOracleCommand::Type_OrthancRestApi:
756 Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release()));
757 break;
758
759 case IOracleCommand::Type_GetOrthancImage:
760 Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release()));
761 break;
762
763 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
764 Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release()));
765 break;
766
767 case IOracleCommand::Type_Sleep:
768 {
769 unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
770 emscripten_set_timeout(TimeoutContext::Callback, timeoutMS,
771 new TimeoutContext(*this, receiver, protection.release()));
772 break;
773 }
774
775 case IOracleCommand::Type_ParseDicomFromWado:
776 #if ORTHANC_ENABLE_DCMTK == 1
777 Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release()));
778 #else
779 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
780 "DCMTK must be enabled to parse DICOM files");
781 #endif
782 break;
783
784 default:
785 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
786 << command->GetType();
787 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
788 }
789
790 return true;
791 }
792
793
794 void WebAssemblyOracle::SetDicomCacheSize(size_t size)
795 {
796 #if ORTHANC_ENABLE_DCMTK == 1
797 if (size == 0)
798 {
799 dicomCache_.reset();
800 }
801 else
802 {
803 dicomCache_.reset(new ParsedDicomCache(size));
804 }
805 #else
806 LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
807 #endif
808 }
809
810
811 WebAssemblyOracle::CachedInstanceAccessor::CachedInstanceAccessor(WebAssemblyOracle& oracle,
812 const std::string& sopInstanceUid)
813 {
814 #if ORTHANC_ENABLE_DCMTK == 1
815 if (oracle.dicomCache_.get() != NULL)
816 {
817 reader_.reset(new ParsedDicomCache::Reader(*oracle.dicomCache_, BUCKET_SOP, sopInstanceUid));
818 }
819 #endif
820 }
821
822 bool WebAssemblyOracle::CachedInstanceAccessor::IsValid() const
823 {
824 #if ORTHANC_ENABLE_DCMTK == 1
825 return (reader_.get() != NULL &&
826 reader_->IsValid());
827 #else
828 return false;
829 #endif
830 }
831
832 #if ORTHANC_ENABLE_DCMTK == 1
833 const Orthanc::ParsedDicomFile& WebAssemblyOracle::CachedInstanceAccessor::GetDicom() const
834 {
835 if (IsValid())
836 {
837 assert(reader_.get() != NULL);
838 return reader_->GetDicom();
839 }
840 else
841 {
842 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
843 }
844 }
845 #endif
846
847 size_t WebAssemblyOracle::CachedInstanceAccessor::GetFileSize() const
848 {
849 #if ORTHANC_ENABLE_DCMTK == 1
850 if (IsValid())
851 {
852 assert(reader_.get() != NULL);
853 return reader_->GetFileSize();
854 }
855 else
856 #endif
857 {
858 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
859 }
860 }
861
862 bool WebAssemblyOracle::CachedInstanceAccessor::HasPixelData() const
863 {
864 #if ORTHANC_ENABLE_DCMTK == 1
865 if (IsValid())
866 {
867 assert(reader_.get() != NULL);
868 return reader_->HasPixelData();
869 }
870 else
871 #endif
872 {
873 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
874 }
875 }
876 }