Mercurial > hg > orthanc-stone
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 } |