Mercurial > hg > orthanc
comparison OrthancServer/Sources/main.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | OrthancServer/main.cpp@e3b3af80732d |
children | 05b8fd21089c |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
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 "PrecompiledHeadersServer.h" | |
35 #include "OrthancRestApi/OrthancRestApi.h" | |
36 | |
37 #include <boost/algorithm/string/predicate.hpp> | |
38 | |
39 #include "../Core/Compatibility.h" | |
40 #include "../Core/DicomFormat/DicomArray.h" | |
41 #include "../Core/DicomNetworking/DicomAssociationParameters.h" | |
42 #include "../Core/DicomNetworking/DicomServer.h" | |
43 #include "../Core/DicomParsing/FromDcmtkBridge.h" | |
44 #include "../Core/HttpServer/FilesystemHttpHandler.h" | |
45 #include "../Core/HttpServer/HttpServer.h" | |
46 #include "../Core/Logging.h" | |
47 #include "../Core/Lua/LuaFunctionCall.h" | |
48 #include "../Plugins/Engine/OrthancPlugins.h" | |
49 #include "EmbeddedResourceHttpHandler.h" | |
50 #include "OrthancConfiguration.h" | |
51 #include "OrthancFindRequestHandler.h" | |
52 #include "OrthancGetRequestHandler.h" | |
53 #include "OrthancInitialization.h" | |
54 #include "OrthancMoveRequestHandler.h" | |
55 #include "ServerContext.h" | |
56 #include "ServerJobs/StorageCommitmentScpJob.h" | |
57 #include "ServerToolbox.h" | |
58 #include "StorageCommitmentReports.h" | |
59 | |
60 using namespace Orthanc; | |
61 | |
62 | |
63 class OrthancStoreRequestHandler : public IStoreRequestHandler | |
64 { | |
65 private: | |
66 ServerContext& context_; | |
67 | |
68 public: | |
69 OrthancStoreRequestHandler(ServerContext& context) : | |
70 context_(context) | |
71 { | |
72 } | |
73 | |
74 | |
75 virtual void Handle(const std::string& dicomFile, | |
76 const DicomMap& dicomSummary, | |
77 const Json::Value& dicomJson, | |
78 const std::string& remoteIp, | |
79 const std::string& remoteAet, | |
80 const std::string& calledAet) | |
81 { | |
82 if (dicomFile.size() > 0) | |
83 { | |
84 DicomInstanceToStore toStore; | |
85 toStore.SetOrigin(DicomInstanceOrigin::FromDicomProtocol | |
86 (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str())); | |
87 toStore.SetBuffer(dicomFile.c_str(), dicomFile.size()); | |
88 toStore.SetSummary(dicomSummary); | |
89 toStore.SetJson(dicomJson); | |
90 | |
91 std::string id; | |
92 context_.Store(id, toStore, StoreInstanceMode_Default); | |
93 } | |
94 } | |
95 }; | |
96 | |
97 | |
98 | |
99 class OrthancStorageCommitmentRequestHandler : public IStorageCommitmentRequestHandler | |
100 { | |
101 private: | |
102 ServerContext& context_; | |
103 | |
104 public: | |
105 OrthancStorageCommitmentRequestHandler(ServerContext& context) : | |
106 context_(context) | |
107 { | |
108 } | |
109 | |
110 virtual void HandleRequest(const std::string& transactionUid, | |
111 const std::vector<std::string>& referencedSopClassUids, | |
112 const std::vector<std::string>& referencedSopInstanceUids, | |
113 const std::string& remoteIp, | |
114 const std::string& remoteAet, | |
115 const std::string& calledAet) | |
116 { | |
117 if (referencedSopClassUids.size() != referencedSopInstanceUids.size()) | |
118 { | |
119 throw OrthancException(ErrorCode_InternalError); | |
120 } | |
121 | |
122 std::unique_ptr<StorageCommitmentScpJob> job( | |
123 new StorageCommitmentScpJob(context_, transactionUid, remoteAet, calledAet)); | |
124 | |
125 for (size_t i = 0; i < referencedSopClassUids.size(); i++) | |
126 { | |
127 job->AddInstance(referencedSopClassUids[i], referencedSopInstanceUids[i]); | |
128 } | |
129 | |
130 job->MarkAsReady(); | |
131 | |
132 context_.GetJobsEngine().GetRegistry().Submit(job.release(), 0 /* default priority */); | |
133 } | |
134 | |
135 virtual void HandleReport(const std::string& transactionUid, | |
136 const std::vector<std::string>& successSopClassUids, | |
137 const std::vector<std::string>& successSopInstanceUids, | |
138 const std::vector<std::string>& failedSopClassUids, | |
139 const std::vector<std::string>& failedSopInstanceUids, | |
140 const std::vector<StorageCommitmentFailureReason>& failureReasons, | |
141 const std::string& remoteIp, | |
142 const std::string& remoteAet, | |
143 const std::string& calledAet) | |
144 { | |
145 if (successSopClassUids.size() != successSopInstanceUids.size() || | |
146 failedSopClassUids.size() != failedSopInstanceUids.size() || | |
147 failedSopClassUids.size() != failureReasons.size()) | |
148 { | |
149 throw OrthancException(ErrorCode_InternalError); | |
150 } | |
151 | |
152 std::unique_ptr<StorageCommitmentReports::Report> report( | |
153 new StorageCommitmentReports::Report(remoteAet)); | |
154 | |
155 for (size_t i = 0; i < successSopClassUids.size(); i++) | |
156 { | |
157 report->AddSuccess(successSopClassUids[i], successSopInstanceUids[i]); | |
158 } | |
159 | |
160 for (size_t i = 0; i < failedSopClassUids.size(); i++) | |
161 { | |
162 report->AddFailure(failedSopClassUids[i], failedSopInstanceUids[i], failureReasons[i]); | |
163 } | |
164 | |
165 report->MarkAsComplete(); | |
166 | |
167 context_.GetStorageCommitmentReports().Store(transactionUid, report.release()); | |
168 } | |
169 }; | |
170 | |
171 | |
172 | |
173 class ModalitiesFromConfiguration : public DicomServer::IRemoteModalities | |
174 { | |
175 public: | |
176 virtual bool IsSameAETitle(const std::string& aet1, | |
177 const std::string& aet2) | |
178 { | |
179 OrthancConfiguration::ReaderLock lock; | |
180 return lock.GetConfiguration().IsSameAETitle(aet1, aet2); | |
181 } | |
182 | |
183 virtual bool LookupAETitle(RemoteModalityParameters& modality, | |
184 const std::string& aet) | |
185 { | |
186 OrthancConfiguration::ReaderLock lock; | |
187 return lock.GetConfiguration().LookupDicomModalityUsingAETitle(modality, aet); | |
188 } | |
189 }; | |
190 | |
191 | |
192 class MyDicomServerFactory : | |
193 public IStoreRequestHandlerFactory, | |
194 public IFindRequestHandlerFactory, | |
195 public IMoveRequestHandlerFactory, | |
196 public IGetRequestHandlerFactory, | |
197 public IStorageCommitmentRequestHandlerFactory | |
198 { | |
199 private: | |
200 ServerContext& context_; | |
201 | |
202 public: | |
203 MyDicomServerFactory(ServerContext& context) : context_(context) | |
204 { | |
205 } | |
206 | |
207 virtual IStoreRequestHandler* ConstructStoreRequestHandler() | |
208 { | |
209 return new OrthancStoreRequestHandler(context_); | |
210 } | |
211 | |
212 virtual IFindRequestHandler* ConstructFindRequestHandler() | |
213 { | |
214 std::unique_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_)); | |
215 | |
216 { | |
217 OrthancConfiguration::ReaderLock lock; | |
218 result->SetMaxResults(lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0)); | |
219 result->SetMaxInstances(lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0)); | |
220 } | |
221 | |
222 if (result->GetMaxResults() == 0) | |
223 { | |
224 LOG(INFO) << "No limit on the number of C-FIND results at the Patient, Study and Series levels"; | |
225 } | |
226 else | |
227 { | |
228 LOG(INFO) << "Maximum " << result->GetMaxResults() | |
229 << " results for C-FIND queries at the Patient, Study and Series levels"; | |
230 } | |
231 | |
232 if (result->GetMaxInstances() == 0) | |
233 { | |
234 LOG(INFO) << "No limit on the number of C-FIND results at the Instance level"; | |
235 } | |
236 else | |
237 { | |
238 LOG(INFO) << "Maximum " << result->GetMaxInstances() | |
239 << " instances will be returned for C-FIND queries at the Instance level"; | |
240 } | |
241 | |
242 return result.release(); | |
243 } | |
244 | |
245 virtual IMoveRequestHandler* ConstructMoveRequestHandler() | |
246 { | |
247 return new OrthancMoveRequestHandler(context_); | |
248 } | |
249 | |
250 virtual IGetRequestHandler* ConstructGetRequestHandler() | |
251 { | |
252 return new OrthancGetRequestHandler(context_); | |
253 } | |
254 | |
255 virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler() | |
256 { | |
257 return new OrthancStorageCommitmentRequestHandler(context_); | |
258 } | |
259 | |
260 | |
261 void Done() | |
262 { | |
263 } | |
264 }; | |
265 | |
266 | |
267 class OrthancApplicationEntityFilter : public IApplicationEntityFilter | |
268 { | |
269 private: | |
270 ServerContext& context_; | |
271 bool alwaysAllowEcho_; | |
272 bool alwaysAllowStore_; | |
273 | |
274 public: | |
275 OrthancApplicationEntityFilter(ServerContext& context) : | |
276 context_(context) | |
277 { | |
278 OrthancConfiguration::ReaderLock lock; | |
279 alwaysAllowEcho_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowEcho", true); | |
280 alwaysAllowStore_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowStore", true); | |
281 } | |
282 | |
283 virtual bool IsAllowedConnection(const std::string& remoteIp, | |
284 const std::string& remoteAet, | |
285 const std::string& calledAet) | |
286 { | |
287 LOG(INFO) << "Incoming connection from AET " << remoteAet | |
288 << " on IP " << remoteIp << ", calling AET " << calledAet; | |
289 | |
290 if (alwaysAllowEcho_ || | |
291 alwaysAllowStore_) | |
292 { | |
293 return true; | |
294 } | |
295 else | |
296 { | |
297 OrthancConfiguration::ReaderLock lock; | |
298 return lock.GetConfiguration().IsKnownAETitle(remoteAet, remoteIp); | |
299 } | |
300 } | |
301 | |
302 virtual bool IsAllowedRequest(const std::string& remoteIp, | |
303 const std::string& remoteAet, | |
304 const std::string& calledAet, | |
305 DicomRequestType type) | |
306 { | |
307 LOG(INFO) << "Incoming " << EnumerationToString(type) << " request from AET " | |
308 << remoteAet << " on IP " << remoteIp << ", calling AET " << calledAet; | |
309 | |
310 if (type == DicomRequestType_Echo && | |
311 alwaysAllowEcho_) | |
312 { | |
313 // Incoming C-Echo requests are always accepted, even from unknown AET | |
314 return true; | |
315 } | |
316 else if (type == DicomRequestType_Store && | |
317 alwaysAllowStore_) | |
318 { | |
319 // Incoming C-Store requests are always accepted, even from unknown AET | |
320 return true; | |
321 } | |
322 else | |
323 { | |
324 OrthancConfiguration::ReaderLock lock; | |
325 | |
326 RemoteModalityParameters modality; | |
327 if (lock.GetConfiguration().LookupDicomModalityUsingAETitle(modality, remoteAet)) | |
328 { | |
329 return modality.IsRequestAllowed(type); | |
330 } | |
331 else | |
332 { | |
333 return false; | |
334 } | |
335 } | |
336 } | |
337 | |
338 virtual bool IsAllowedTransferSyntax(const std::string& remoteIp, | |
339 const std::string& remoteAet, | |
340 const std::string& calledAet, | |
341 TransferSyntax syntax) | |
342 { | |
343 std::string configuration; | |
344 | |
345 switch (syntax) | |
346 { | |
347 case TransferSyntax_Deflated: | |
348 configuration = "DeflatedTransferSyntaxAccepted"; | |
349 break; | |
350 | |
351 case TransferSyntax_Jpeg: | |
352 configuration = "JpegTransferSyntaxAccepted"; | |
353 break; | |
354 | |
355 case TransferSyntax_Jpeg2000: | |
356 configuration = "Jpeg2000TransferSyntaxAccepted"; | |
357 break; | |
358 | |
359 case TransferSyntax_JpegLossless: | |
360 configuration = "JpegLosslessTransferSyntaxAccepted"; | |
361 break; | |
362 | |
363 case TransferSyntax_Jpip: | |
364 configuration = "JpipTransferSyntaxAccepted"; | |
365 break; | |
366 | |
367 case TransferSyntax_Mpeg2: | |
368 configuration = "Mpeg2TransferSyntaxAccepted"; | |
369 break; | |
370 | |
371 case TransferSyntax_Mpeg4: | |
372 configuration = "Mpeg4TransferSyntaxAccepted"; | |
373 break; | |
374 | |
375 case TransferSyntax_Rle: | |
376 configuration = "RleTransferSyntaxAccepted"; | |
377 break; | |
378 | |
379 default: | |
380 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
381 } | |
382 | |
383 { | |
384 std::string name = "Is" + configuration; | |
385 | |
386 LuaScripting::Lock lock(context_.GetLuaScripting()); | |
387 | |
388 if (lock.GetLua().IsExistingFunction(name.c_str())) | |
389 { | |
390 LuaFunctionCall call(lock.GetLua(), name.c_str()); | |
391 call.PushString(remoteAet); | |
392 call.PushString(remoteIp); | |
393 call.PushString(calledAet); | |
394 return call.ExecutePredicate(); | |
395 } | |
396 } | |
397 | |
398 { | |
399 OrthancConfiguration::ReaderLock lock; | |
400 return lock.GetConfiguration().GetBooleanParameter(configuration, true); | |
401 } | |
402 } | |
403 | |
404 | |
405 virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, | |
406 const std::string& remoteAet, | |
407 const std::string& calledAet) | |
408 { | |
409 static const char* configuration = "UnknownSopClassAccepted"; | |
410 | |
411 { | |
412 std::string lua = "Is" + std::string(configuration); | |
413 | |
414 LuaScripting::Lock lock(context_.GetLuaScripting()); | |
415 | |
416 if (lock.GetLua().IsExistingFunction(lua.c_str())) | |
417 { | |
418 LuaFunctionCall call(lock.GetLua(), lua.c_str()); | |
419 call.PushString(remoteAet); | |
420 call.PushString(remoteIp); | |
421 call.PushString(calledAet); | |
422 return call.ExecutePredicate(); | |
423 } | |
424 } | |
425 | |
426 { | |
427 OrthancConfiguration::ReaderLock lock; | |
428 return lock.GetConfiguration().GetBooleanParameter(configuration, false); | |
429 } | |
430 } | |
431 }; | |
432 | |
433 | |
434 class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter | |
435 { | |
436 private: | |
437 ServerContext& context_; | |
438 OrthancPlugins* plugins_; | |
439 | |
440 public: | |
441 MyIncomingHttpRequestFilter(ServerContext& context, | |
442 OrthancPlugins* plugins) : | |
443 context_(context), | |
444 plugins_(plugins) | |
445 { | |
446 } | |
447 | |
448 virtual bool IsAllowed(HttpMethod method, | |
449 const char* uri, | |
450 const char* ip, | |
451 const char* username, | |
452 const IHttpHandler::Arguments& httpHeaders, | |
453 const IHttpHandler::GetArguments& getArguments) | |
454 { | |
455 #if ORTHANC_ENABLE_PLUGINS == 1 | |
456 if (plugins_ != NULL && | |
457 !plugins_->IsAllowed(method, uri, ip, username, httpHeaders, getArguments)) | |
458 { | |
459 return false; | |
460 } | |
461 #endif | |
462 | |
463 static const char* HTTP_FILTER = "IncomingHttpRequestFilter"; | |
464 | |
465 LuaScripting::Lock lock(context_.GetLuaScripting()); | |
466 | |
467 // Test if the instance must be filtered out | |
468 if (lock.GetLua().IsExistingFunction(HTTP_FILTER)) | |
469 { | |
470 LuaFunctionCall call(lock.GetLua(), HTTP_FILTER); | |
471 | |
472 switch (method) | |
473 { | |
474 case HttpMethod_Get: | |
475 call.PushString("GET"); | |
476 break; | |
477 | |
478 case HttpMethod_Put: | |
479 call.PushString("PUT"); | |
480 break; | |
481 | |
482 case HttpMethod_Post: | |
483 call.PushString("POST"); | |
484 break; | |
485 | |
486 case HttpMethod_Delete: | |
487 call.PushString("DELETE"); | |
488 break; | |
489 | |
490 default: | |
491 return true; | |
492 } | |
493 | |
494 call.PushString(uri); | |
495 call.PushString(ip); | |
496 call.PushString(username); | |
497 call.PushStringMap(httpHeaders); | |
498 | |
499 if (!call.ExecutePredicate()) | |
500 { | |
501 LOG(INFO) << "An incoming HTTP request has been discarded by the filter"; | |
502 return false; | |
503 } | |
504 } | |
505 | |
506 return true; | |
507 } | |
508 }; | |
509 | |
510 | |
511 | |
512 class MyHttpExceptionFormatter : public IHttpExceptionFormatter | |
513 { | |
514 private: | |
515 bool describeErrors_; | |
516 OrthancPlugins* plugins_; | |
517 | |
518 public: | |
519 MyHttpExceptionFormatter(bool describeErrors, | |
520 OrthancPlugins* plugins) : | |
521 describeErrors_(describeErrors), | |
522 plugins_(plugins) | |
523 { | |
524 } | |
525 | |
526 virtual void Format(HttpOutput& output, | |
527 const OrthancException& exception, | |
528 HttpMethod method, | |
529 const char* uri) | |
530 { | |
531 { | |
532 bool isPlugin = false; | |
533 | |
534 #if ORTHANC_ENABLE_PLUGINS == 1 | |
535 if (plugins_ != NULL) | |
536 { | |
537 plugins_->GetErrorDictionary().LogError(exception.GetErrorCode(), true); | |
538 isPlugin = true; | |
539 } | |
540 #endif | |
541 | |
542 if (!isPlugin) | |
543 { | |
544 LOG(ERROR) << "Exception in the HTTP handler: " << exception.What(); | |
545 } | |
546 } | |
547 | |
548 Json::Value message = Json::objectValue; | |
549 ErrorCode errorCode = exception.GetErrorCode(); | |
550 HttpStatus httpStatus = exception.GetHttpStatus(); | |
551 | |
552 { | |
553 bool isPlugin = false; | |
554 | |
555 #if ORTHANC_ENABLE_PLUGINS == 1 | |
556 if (plugins_ != NULL && | |
557 plugins_->GetErrorDictionary().Format(message, httpStatus, exception)) | |
558 { | |
559 errorCode = ErrorCode_Plugin; | |
560 isPlugin = true; | |
561 } | |
562 #endif | |
563 | |
564 if (!isPlugin) | |
565 { | |
566 message["Message"] = exception.What(); | |
567 } | |
568 } | |
569 | |
570 if (!describeErrors_) | |
571 { | |
572 output.SendStatus(httpStatus); | |
573 } | |
574 else | |
575 { | |
576 message["Method"] = EnumerationToString(method); | |
577 message["Uri"] = uri; | |
578 message["HttpError"] = EnumerationToString(httpStatus); | |
579 message["HttpStatus"] = httpStatus; | |
580 message["OrthancError"] = EnumerationToString(errorCode); | |
581 message["OrthancStatus"] = errorCode; | |
582 | |
583 if (exception.HasDetails()) | |
584 { | |
585 message["Details"] = exception.GetDetails(); | |
586 } | |
587 | |
588 std::string info = message.toStyledString(); | |
589 output.SendStatus(httpStatus, info); | |
590 } | |
591 } | |
592 }; | |
593 | |
594 | |
595 | |
596 static void PrintHelp(const char* path) | |
597 { | |
598 std::cout | |
599 << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl | |
600 << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." << std::endl | |
601 << std::endl | |
602 << "The \"CONFIGURATION\" argument can be a single file or a directory. In the " << std::endl | |
603 << "case of a directory, all the JSON files it contains will be merged. " << std::endl | |
604 << "If no configuration path is given on the command line, a set of default " << std::endl | |
605 << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl | |
606 << "instructions about how to use Orthanc <http://www.orthanc-server.com/>." << std::endl | |
607 << std::endl | |
608 << "Command-line options:" << std::endl | |
609 << " --help\t\tdisplay this help and exit" << std::endl | |
610 << " --logdir=[dir]\tdirectory where to store the log files" << std::endl | |
611 << "\t\t\t(by default, the log is dumped to stderr)" << std::endl | |
612 << " --logfile=[file]\tfile where to store the log of Orthanc" << std::endl | |
613 << "\t\t\t(by default, the log is dumped to stderr)" << std::endl | |
614 << " --config=[file]\tcreate a sample configuration file and exit" << std::endl | |
615 << "\t\t\t(if file is \"-\", dumps to stdout)" << std::endl | |
616 << " --errors\t\tprint the supported error codes and exit" << std::endl | |
617 << " --verbose\t\tbe verbose in logs" << std::endl | |
618 << " --trace\t\thighest verbosity in logs (for debug)" << std::endl | |
619 << " --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl | |
620 << "\t\t\tdatabase (beware that the database will become" << std::endl | |
621 << "\t\t\tincompatible with former versions of Orthanc)" << std::endl | |
622 << " --no-jobs\t\tDon't restart the jobs that were stored during" << std::endl | |
623 << "\t\t\tthe last execution of Orthanc" << std::endl | |
624 << " --version\t\toutput version information and exit" << std::endl | |
625 << std::endl | |
626 << "Exit status:" << std::endl | |
627 << " 0 if success," << std::endl | |
628 #if defined(_WIN32) | |
629 << "!= 0 if error (use the --errors option to get the list of possible errors)." << std::endl | |
630 #else | |
631 << " -1 if error (have a look at the logs)." << std::endl | |
632 #endif | |
633 << std::endl; | |
634 } | |
635 | |
636 | |
637 static void PrintVersion(const char* path) | |
638 { | |
639 std::cout | |
640 << path << " " << ORTHANC_VERSION << std::endl | |
641 << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl | |
642 << "Copyright (C) 2017-2020 Osimis S.A. (Belgium)" << std::endl | |
643 << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl | |
644 << "This is free software: you are free to change and redistribute it." << std::endl | |
645 << "There is NO WARRANTY, to the extent permitted by law." << std::endl | |
646 << std::endl | |
647 << "Written by Sebastien Jodogne <s.jodogne@orthanc-labs.com>" << std::endl; | |
648 } | |
649 | |
650 | |
651 static void PrintErrorCode(ErrorCode code, const char* description) | |
652 { | |
653 std::cout | |
654 << std::right << std::setw(16) | |
655 << static_cast<int>(code) | |
656 << " " << description << std::endl; | |
657 } | |
658 | |
659 | |
660 static void PrintErrors(const char* path) | |
661 { | |
662 std::cout | |
663 << path << " " << ORTHANC_VERSION << std::endl | |
664 << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." | |
665 << std::endl << std::endl | |
666 << "List of error codes that could be returned by Orthanc:" | |
667 << std::endl << std::endl; | |
668 | |
669 // The content of the following brackets is automatically generated | |
670 // by the "GenerateErrorCodes.py" script | |
671 { | |
672 PrintErrorCode(ErrorCode_InternalError, "Internal error"); | |
673 PrintErrorCode(ErrorCode_Success, "Success"); | |
674 PrintErrorCode(ErrorCode_Plugin, "Error encountered within the plugin engine"); | |
675 PrintErrorCode(ErrorCode_NotImplemented, "Not implemented yet"); | |
676 PrintErrorCode(ErrorCode_ParameterOutOfRange, "Parameter out of range"); | |
677 PrintErrorCode(ErrorCode_NotEnoughMemory, "The server hosting Orthanc is running out of memory"); | |
678 PrintErrorCode(ErrorCode_BadParameterType, "Bad type for a parameter"); | |
679 PrintErrorCode(ErrorCode_BadSequenceOfCalls, "Bad sequence of calls"); | |
680 PrintErrorCode(ErrorCode_InexistentItem, "Accessing an inexistent item"); | |
681 PrintErrorCode(ErrorCode_BadRequest, "Bad request"); | |
682 PrintErrorCode(ErrorCode_NetworkProtocol, "Error in the network protocol"); | |
683 PrintErrorCode(ErrorCode_SystemCommand, "Error while calling a system command"); | |
684 PrintErrorCode(ErrorCode_Database, "Error with the database engine"); | |
685 PrintErrorCode(ErrorCode_UriSyntax, "Badly formatted URI"); | |
686 PrintErrorCode(ErrorCode_InexistentFile, "Inexistent file"); | |
687 PrintErrorCode(ErrorCode_CannotWriteFile, "Cannot write to file"); | |
688 PrintErrorCode(ErrorCode_BadFileFormat, "Bad file format"); | |
689 PrintErrorCode(ErrorCode_Timeout, "Timeout"); | |
690 PrintErrorCode(ErrorCode_UnknownResource, "Unknown resource"); | |
691 PrintErrorCode(ErrorCode_IncompatibleDatabaseVersion, "Incompatible version of the database"); | |
692 PrintErrorCode(ErrorCode_FullStorage, "The file storage is full"); | |
693 PrintErrorCode(ErrorCode_CorruptedFile, "Corrupted file (e.g. inconsistent MD5 hash)"); | |
694 PrintErrorCode(ErrorCode_InexistentTag, "Inexistent tag"); | |
695 PrintErrorCode(ErrorCode_ReadOnly, "Cannot modify a read-only data structure"); | |
696 PrintErrorCode(ErrorCode_IncompatibleImageFormat, "Incompatible format of the images"); | |
697 PrintErrorCode(ErrorCode_IncompatibleImageSize, "Incompatible size of the images"); | |
698 PrintErrorCode(ErrorCode_SharedLibrary, "Error while using a shared library (plugin)"); | |
699 PrintErrorCode(ErrorCode_UnknownPluginService, "Plugin invoking an unknown service"); | |
700 PrintErrorCode(ErrorCode_UnknownDicomTag, "Unknown DICOM tag"); | |
701 PrintErrorCode(ErrorCode_BadJson, "Cannot parse a JSON document"); | |
702 PrintErrorCode(ErrorCode_Unauthorized, "Bad credentials were provided to an HTTP request"); | |
703 PrintErrorCode(ErrorCode_BadFont, "Badly formatted font file"); | |
704 PrintErrorCode(ErrorCode_DatabasePlugin, "The plugin implementing a custom database back-end does not fulfill the proper interface"); | |
705 PrintErrorCode(ErrorCode_StorageAreaPlugin, "Error in the plugin implementing a custom storage area"); | |
706 PrintErrorCode(ErrorCode_EmptyRequest, "The request is empty"); | |
707 PrintErrorCode(ErrorCode_NotAcceptable, "Cannot send a response which is acceptable according to the Accept HTTP header"); | |
708 PrintErrorCode(ErrorCode_NullPointer, "Cannot handle a NULL pointer"); | |
709 PrintErrorCode(ErrorCode_DatabaseUnavailable, "The database is currently not available (probably a transient situation)"); | |
710 PrintErrorCode(ErrorCode_CanceledJob, "This job was canceled"); | |
711 PrintErrorCode(ErrorCode_BadGeometry, "Geometry error encountered in Stone"); | |
712 PrintErrorCode(ErrorCode_SslInitialization, "Cannot initialize SSL encryption, check out your certificates"); | |
713 PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); | |
714 PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); | |
715 PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database"); | |
716 PrintErrorCode(ErrorCode_SQLiteStatementAlreadyUsed, "SQLite: This cached statement is already being referred to"); | |
717 PrintErrorCode(ErrorCode_SQLiteExecute, "SQLite: Cannot execute a command"); | |
718 PrintErrorCode(ErrorCode_SQLiteRollbackWithoutTransaction, "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)"); | |
719 PrintErrorCode(ErrorCode_SQLiteCommitWithoutTransaction, "SQLite: Committing a nonexistent transaction"); | |
720 PrintErrorCode(ErrorCode_SQLiteRegisterFunction, "SQLite: Unable to register a function"); | |
721 PrintErrorCode(ErrorCode_SQLiteFlush, "SQLite: Unable to flush the database"); | |
722 PrintErrorCode(ErrorCode_SQLiteCannotRun, "SQLite: Cannot run a cached statement"); | |
723 PrintErrorCode(ErrorCode_SQLiteCannotStep, "SQLite: Cannot step over a cached statement"); | |
724 PrintErrorCode(ErrorCode_SQLiteBindOutOfRange, "SQLite: Bing a value while out of range (serious error)"); | |
725 PrintErrorCode(ErrorCode_SQLitePrepareStatement, "SQLite: Cannot prepare a cached statement"); | |
726 PrintErrorCode(ErrorCode_SQLiteTransactionAlreadyStarted, "SQLite: Beginning the same transaction twice"); | |
727 PrintErrorCode(ErrorCode_SQLiteTransactionCommit, "SQLite: Failure when committing the transaction"); | |
728 PrintErrorCode(ErrorCode_SQLiteTransactionBegin, "SQLite: Cannot start a transaction"); | |
729 PrintErrorCode(ErrorCode_DirectoryOverFile, "The directory to be created is already occupied by a regular file"); | |
730 PrintErrorCode(ErrorCode_FileStorageCannotWrite, "Unable to create a subdirectory or a file in the file storage"); | |
731 PrintErrorCode(ErrorCode_DirectoryExpected, "The specified path does not point to a directory"); | |
732 PrintErrorCode(ErrorCode_HttpPortInUse, "The TCP port of the HTTP server is privileged or already in use"); | |
733 PrintErrorCode(ErrorCode_DicomPortInUse, "The TCP port of the DICOM server is privileged or already in use"); | |
734 PrintErrorCode(ErrorCode_BadHttpStatusInRest, "This HTTP status is not allowed in a REST API"); | |
735 PrintErrorCode(ErrorCode_RegularFileExpected, "The specified path does not point to a regular file"); | |
736 PrintErrorCode(ErrorCode_PathToExecutable, "Unable to get the path to the executable"); | |
737 PrintErrorCode(ErrorCode_MakeDirectory, "Cannot create a directory"); | |
738 PrintErrorCode(ErrorCode_BadApplicationEntityTitle, "An application entity title (AET) cannot be empty or be longer than 16 characters"); | |
739 PrintErrorCode(ErrorCode_NoCFindHandler, "No request handler factory for DICOM C-FIND SCP"); | |
740 PrintErrorCode(ErrorCode_NoCMoveHandler, "No request handler factory for DICOM C-MOVE SCP"); | |
741 PrintErrorCode(ErrorCode_NoCStoreHandler, "No request handler factory for DICOM C-STORE SCP"); | |
742 PrintErrorCode(ErrorCode_NoApplicationEntityFilter, "No application entity filter"); | |
743 PrintErrorCode(ErrorCode_NoSopClassOrInstance, "DicomUserConnection: Unable to find the SOP class and instance"); | |
744 PrintErrorCode(ErrorCode_NoPresentationContext, "DicomUserConnection: No acceptable presentation context for modality"); | |
745 PrintErrorCode(ErrorCode_DicomFindUnavailable, "DicomUserConnection: The C-FIND command is not supported by the remote SCP"); | |
746 PrintErrorCode(ErrorCode_DicomMoveUnavailable, "DicomUserConnection: The C-MOVE command is not supported by the remote SCP"); | |
747 PrintErrorCode(ErrorCode_CannotStoreInstance, "Cannot store an instance"); | |
748 PrintErrorCode(ErrorCode_CreateDicomNotString, "Only string values are supported when creating DICOM instances"); | |
749 PrintErrorCode(ErrorCode_CreateDicomOverrideTag, "Trying to override a value inherited from a parent module"); | |
750 PrintErrorCode(ErrorCode_CreateDicomUseContent, "Use \"Content\" to inject an image into a new DICOM instance"); | |
751 PrintErrorCode(ErrorCode_CreateDicomNoPayload, "No payload is present for one instance in the series"); | |
752 PrintErrorCode(ErrorCode_CreateDicomUseDataUriScheme, "The payload of the DICOM instance must be specified according to Data URI scheme"); | |
753 PrintErrorCode(ErrorCode_CreateDicomBadParent, "Trying to attach a new DICOM instance to an inexistent resource"); | |
754 PrintErrorCode(ErrorCode_CreateDicomParentIsInstance, "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)"); | |
755 PrintErrorCode(ErrorCode_CreateDicomParentEncoding, "Unable to get the encoding of the parent resource"); | |
756 PrintErrorCode(ErrorCode_UnknownModality, "Unknown modality"); | |
757 PrintErrorCode(ErrorCode_BadJobOrdering, "Bad ordering of filters in a job"); | |
758 PrintErrorCode(ErrorCode_JsonToLuaTable, "Cannot convert the given JSON object to a Lua table"); | |
759 PrintErrorCode(ErrorCode_CannotCreateLua, "Cannot create the Lua context"); | |
760 PrintErrorCode(ErrorCode_CannotExecuteLua, "Cannot execute a Lua command"); | |
761 PrintErrorCode(ErrorCode_LuaAlreadyExecuted, "Arguments cannot be pushed after the Lua function is executed"); | |
762 PrintErrorCode(ErrorCode_LuaBadOutput, "The Lua function does not give the expected number of outputs"); | |
763 PrintErrorCode(ErrorCode_NotLuaPredicate, "The Lua function is not a predicate (only true/false outputs allowed)"); | |
764 PrintErrorCode(ErrorCode_LuaReturnsNoString, "The Lua function does not return a string"); | |
765 PrintErrorCode(ErrorCode_StorageAreaAlreadyRegistered, "Another plugin has already registered a custom storage area"); | |
766 PrintErrorCode(ErrorCode_DatabaseBackendAlreadyRegistered, "Another plugin has already registered a custom database back-end"); | |
767 PrintErrorCode(ErrorCode_DatabaseNotInitialized, "Plugin trying to call the database during its initialization"); | |
768 PrintErrorCode(ErrorCode_SslDisabled, "Orthanc has been built without SSL support"); | |
769 PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series"); | |
770 PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP"); | |
771 PrintErrorCode(ErrorCode_AlreadyExistingTag, "Cannot override the value of a tag that already exists"); | |
772 PrintErrorCode(ErrorCode_NoCGetHandler, "No request handler factory for DICOM C-GET SCP"); | |
773 PrintErrorCode(ErrorCode_NoStorageCommitmentHandler, "No request handler factory for DICOM N-ACTION SCP (storage commitment)"); | |
774 PrintErrorCode(ErrorCode_UnsupportedMediaType, "Unsupported media type"); | |
775 } | |
776 | |
777 std::cout << std::endl; | |
778 } | |
779 | |
780 | |
781 | |
782 #if ORTHANC_ENABLE_PLUGINS == 1 | |
783 static void LoadPlugins(OrthancPlugins& plugins) | |
784 { | |
785 std::list<std::string> path; | |
786 | |
787 { | |
788 OrthancConfiguration::ReaderLock lock; | |
789 lock.GetConfiguration().GetListOfStringsParameter(path, "Plugins"); | |
790 } | |
791 | |
792 for (std::list<std::string>::const_iterator | |
793 it = path.begin(); it != path.end(); ++it) | |
794 { | |
795 std::string path; | |
796 | |
797 { | |
798 OrthancConfiguration::ReaderLock lock; | |
799 path = lock.GetConfiguration().InterpretStringParameterAsPath(*it); | |
800 } | |
801 | |
802 LOG(WARNING) << "Loading plugin(s) from: " << path; | |
803 plugins.GetManager().RegisterPlugin(path); | |
804 } | |
805 } | |
806 #endif | |
807 | |
808 | |
809 | |
810 // Returns "true" if restart is required | |
811 static bool WaitForExit(ServerContext& context, | |
812 OrthancRestApi& restApi) | |
813 { | |
814 LOG(WARNING) << "Orthanc has started"; | |
815 | |
816 #if ORTHANC_ENABLE_PLUGINS == 1 | |
817 if (context.HasPlugins()) | |
818 { | |
819 context.GetPlugins().SignalOrthancStarted(); | |
820 } | |
821 #endif | |
822 | |
823 context.GetLuaScripting().Start(); | |
824 context.GetLuaScripting().Execute("Initialize"); | |
825 | |
826 bool restart; | |
827 | |
828 for (;;) | |
829 { | |
830 ServerBarrierEvent event = SystemToolbox::ServerBarrier(restApi.LeaveBarrierFlag()); | |
831 restart = restApi.IsResetRequestReceived(); | |
832 | |
833 if (!restart && | |
834 event == ServerBarrierEvent_Reload) | |
835 { | |
836 // Handling of SIGHUP | |
837 | |
838 OrthancConfiguration::ReaderLock lock; | |
839 if (lock.GetConfiguration().HasConfigurationChanged()) | |
840 { | |
841 LOG(WARNING) << "A SIGHUP signal has been received, resetting Orthanc"; | |
842 Logging::Flush(); | |
843 restart = true; | |
844 break; | |
845 } | |
846 else | |
847 { | |
848 LOG(WARNING) << "A SIGHUP signal has been received, but is ignored " | |
849 << "as the configuration has not changed on the disk"; | |
850 Logging::Flush(); | |
851 continue; | |
852 } | |
853 } | |
854 else | |
855 { | |
856 break; | |
857 } | |
858 } | |
859 | |
860 context.GetLuaScripting().Execute("Finalize"); | |
861 context.GetLuaScripting().Stop(); | |
862 | |
863 #if ORTHANC_ENABLE_PLUGINS == 1 | |
864 if (context.HasPlugins()) | |
865 { | |
866 context.GetPlugins().SignalOrthancStopped(); | |
867 } | |
868 #endif | |
869 | |
870 if (restart) | |
871 { | |
872 LOG(WARNING) << "Reset request received, restarting Orthanc"; | |
873 } | |
874 | |
875 // We're done | |
876 LOG(WARNING) << "Orthanc is stopping"; | |
877 | |
878 return restart; | |
879 } | |
880 | |
881 | |
882 | |
883 static bool StartHttpServer(ServerContext& context, | |
884 OrthancRestApi& restApi, | |
885 OrthancPlugins* plugins) | |
886 { | |
887 bool httpServerEnabled; | |
888 | |
889 { | |
890 OrthancConfiguration::ReaderLock lock; | |
891 httpServerEnabled = lock.GetConfiguration().GetBooleanParameter("HttpServerEnabled", true); | |
892 } | |
893 | |
894 if (!httpServerEnabled) | |
895 { | |
896 LOG(WARNING) << "The HTTP server is disabled"; | |
897 return WaitForExit(context, restApi); | |
898 } | |
899 else | |
900 { | |
901 MyIncomingHttpRequestFilter httpFilter(context, plugins); | |
902 HttpServer httpServer; | |
903 bool httpDescribeErrors; | |
904 | |
905 #if ORTHANC_ENABLE_MONGOOSE == 1 | |
906 const bool defaultKeepAlive = false; | |
907 #elif ORTHANC_ENABLE_CIVETWEB == 1 | |
908 const bool defaultKeepAlive = true; | |
909 #else | |
910 # error "Either Mongoose or Civetweb must be enabled to compile this file" | |
911 #endif | |
912 | |
913 { | |
914 OrthancConfiguration::ReaderLock lock; | |
915 | |
916 httpDescribeErrors = lock.GetConfiguration().GetBooleanParameter("HttpDescribeErrors", true); | |
917 | |
918 // HTTP server | |
919 httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 50)); | |
920 httpServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042)); | |
921 httpServer.SetRemoteAccessAllowed(lock.GetConfiguration().GetBooleanParameter("RemoteAccessAllowed", false)); | |
922 httpServer.SetKeepAliveEnabled(lock.GetConfiguration().GetBooleanParameter("KeepAlive", defaultKeepAlive)); | |
923 httpServer.SetHttpCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("HttpCompressionEnabled", true)); | |
924 httpServer.SetTcpNoDelay(lock.GetConfiguration().GetBooleanParameter("TcpNoDelay", true)); | |
925 httpServer.SetRequestTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpRequestTimeout", 30)); | |
926 | |
927 // Let's assume that the HTTP server is secure | |
928 context.SetHttpServerSecure(true); | |
929 | |
930 bool authenticationEnabled; | |
931 if (lock.GetConfiguration().LookupBooleanParameter(authenticationEnabled, "AuthenticationEnabled")) | |
932 { | |
933 httpServer.SetAuthenticationEnabled(authenticationEnabled); | |
934 | |
935 if (httpServer.IsRemoteAccessAllowed() && | |
936 !authenticationEnabled) | |
937 { | |
938 LOG(WARNING) << "====> Remote access is enabled while user authentication is explicitly disabled, " | |
939 << "your setup is POSSIBLY INSECURE <===="; | |
940 context.SetHttpServerSecure(false); | |
941 } | |
942 } | |
943 else if (httpServer.IsRemoteAccessAllowed()) | |
944 { | |
945 // Starting with Orthanc 1.5.8, it is impossible to enable | |
946 // remote access without having explicitly disabled user | |
947 // authentication. | |
948 LOG(WARNING) << "Remote access is allowed but \"AuthenticationEnabled\" is not in the configuration, " | |
949 << "automatically enabling HTTP authentication for security"; | |
950 httpServer.SetAuthenticationEnabled(true); | |
951 } | |
952 else | |
953 { | |
954 // If Orthanc only listens on the localhost, it is OK to have | |
955 // "AuthenticationEnabled" disabled | |
956 httpServer.SetAuthenticationEnabled(false); | |
957 } | |
958 | |
959 bool hasUsers = lock.GetConfiguration().SetupRegisteredUsers(httpServer); | |
960 | |
961 if (httpServer.IsAuthenticationEnabled() && | |
962 !hasUsers) | |
963 { | |
964 if (httpServer.IsRemoteAccessAllowed()) | |
965 { | |
966 /** | |
967 * Starting with Orthanc 1.5.8, if no user is explicitly | |
968 * defined while remote access is allowed, we create a | |
969 * default user, and Orthanc Explorer shows a warning | |
970 * message about an "Insecure setup". This convention is | |
971 * used in Docker images "jodogne/orthanc", | |
972 * "jodogne/orthanc-plugins" and "osimis/orthanc". | |
973 **/ | |
974 LOG(WARNING) << "====> HTTP authentication is enabled, but no user is declared. " | |
975 << "Creating a default user: Review your configuration option \"RegisteredUsers\". " | |
976 << "Your setup is INSECURE <===="; | |
977 | |
978 context.SetHttpServerSecure(false); | |
979 | |
980 // This is the username/password of the default user in Orthanc. | |
981 httpServer.RegisterUser("orthanc", "orthanc"); | |
982 } | |
983 else | |
984 { | |
985 LOG(WARNING) << "HTTP authentication is enabled, but no user is declared, " | |
986 << "check the value of configuration option \"RegisteredUsers\""; | |
987 } | |
988 } | |
989 | |
990 if (lock.GetConfiguration().GetBooleanParameter("SslEnabled", false)) | |
991 { | |
992 std::string certificate = lock.GetConfiguration().InterpretStringParameterAsPath( | |
993 lock.GetConfiguration().GetStringParameter("SslCertificate", "certificate.pem")); | |
994 httpServer.SetSslEnabled(true); | |
995 httpServer.SetSslCertificate(certificate.c_str()); | |
996 } | |
997 else | |
998 { | |
999 httpServer.SetSslEnabled(false); | |
1000 } | |
1001 | |
1002 if (lock.GetConfiguration().GetBooleanParameter("ExecuteLuaEnabled", false)) | |
1003 { | |
1004 context.SetExecuteLuaEnabled(true); | |
1005 LOG(WARNING) << "====> Remote LUA script execution is enabled. Review your configuration option \"ExecuteLuaEnabled\". " | |
1006 << "Your setup is POSSIBLY INSECURE <===="; | |
1007 } | |
1008 else | |
1009 { | |
1010 context.SetExecuteLuaEnabled(false); | |
1011 LOG(WARNING) << "Remote LUA script execution is disabled"; | |
1012 } | |
1013 } | |
1014 | |
1015 MyHttpExceptionFormatter exceptionFormatter(httpDescribeErrors, plugins); | |
1016 | |
1017 httpServer.SetIncomingHttpRequestFilter(httpFilter); | |
1018 httpServer.SetHttpExceptionFormatter(exceptionFormatter); | |
1019 httpServer.Register(context.GetHttpHandler()); | |
1020 | |
1021 if (httpServer.GetPortNumber() < 1024) | |
1022 { | |
1023 LOG(WARNING) << "The HTTP port is privileged (" | |
1024 << httpServer.GetPortNumber() << " is below 1024), " | |
1025 << "make sure you run Orthanc as root/administrator"; | |
1026 } | |
1027 | |
1028 httpServer.Start(); | |
1029 | |
1030 bool restart = WaitForExit(context, restApi); | |
1031 | |
1032 httpServer.Stop(); | |
1033 LOG(WARNING) << " HTTP server has stopped"; | |
1034 | |
1035 return restart; | |
1036 } | |
1037 } | |
1038 | |
1039 | |
1040 static bool StartDicomServer(ServerContext& context, | |
1041 OrthancRestApi& restApi, | |
1042 OrthancPlugins* plugins) | |
1043 { | |
1044 bool dicomServerEnabled; | |
1045 | |
1046 { | |
1047 OrthancConfiguration::ReaderLock lock; | |
1048 dicomServerEnabled = lock.GetConfiguration().GetBooleanParameter("DicomServerEnabled", true); | |
1049 } | |
1050 | |
1051 if (!dicomServerEnabled) | |
1052 { | |
1053 LOG(WARNING) << "The DICOM server is disabled"; | |
1054 return StartHttpServer(context, restApi, plugins); | |
1055 } | |
1056 else | |
1057 { | |
1058 MyDicomServerFactory serverFactory(context); | |
1059 OrthancApplicationEntityFilter dicomFilter(context); | |
1060 ModalitiesFromConfiguration modalities; | |
1061 | |
1062 // Setup the DICOM server | |
1063 DicomServer dicomServer; | |
1064 dicomServer.SetRemoteModalities(modalities); | |
1065 dicomServer.SetStoreRequestHandlerFactory(serverFactory); | |
1066 dicomServer.SetMoveRequestHandlerFactory(serverFactory); | |
1067 dicomServer.SetGetRequestHandlerFactory(serverFactory); | |
1068 dicomServer.SetFindRequestHandlerFactory(serverFactory); | |
1069 dicomServer.SetStorageCommitmentRequestHandlerFactory(serverFactory); | |
1070 | |
1071 { | |
1072 OrthancConfiguration::ReaderLock lock; | |
1073 dicomServer.SetCalledApplicationEntityTitleCheck(lock.GetConfiguration().GetBooleanParameter("DicomCheckCalledAet", false)); | |
1074 dicomServer.SetAssociationTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScpTimeout", 30)); | |
1075 dicomServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomPort", 4242)); | |
1076 dicomServer.SetApplicationEntityTitle(lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC")); | |
1077 } | |
1078 | |
1079 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1080 if (plugins != NULL) | |
1081 { | |
1082 if (plugins->HasWorklistHandler()) | |
1083 { | |
1084 dicomServer.SetWorklistRequestHandlerFactory(*plugins); | |
1085 } | |
1086 | |
1087 if (plugins->HasFindHandler()) | |
1088 { | |
1089 dicomServer.SetFindRequestHandlerFactory(*plugins); | |
1090 } | |
1091 | |
1092 if (plugins->HasMoveHandler()) | |
1093 { | |
1094 dicomServer.SetMoveRequestHandlerFactory(*plugins); | |
1095 } | |
1096 } | |
1097 #endif | |
1098 | |
1099 dicomServer.SetApplicationEntityFilter(dicomFilter); | |
1100 | |
1101 if (dicomServer.GetPortNumber() < 1024) | |
1102 { | |
1103 LOG(WARNING) << "The DICOM port is privileged (" | |
1104 << dicomServer.GetPortNumber() << " is below 1024), " | |
1105 << "make sure you run Orthanc as root/administrator"; | |
1106 } | |
1107 | |
1108 dicomServer.Start(); | |
1109 LOG(WARNING) << "DICOM server listening with AET " << dicomServer.GetApplicationEntityTitle() | |
1110 << " on port: " << dicomServer.GetPortNumber(); | |
1111 | |
1112 bool restart = false; | |
1113 ErrorCode error = ErrorCode_Success; | |
1114 | |
1115 try | |
1116 { | |
1117 restart = StartHttpServer(context, restApi, plugins); | |
1118 } | |
1119 catch (OrthancException& e) | |
1120 { | |
1121 error = e.GetErrorCode(); | |
1122 } | |
1123 | |
1124 dicomServer.Stop(); | |
1125 LOG(WARNING) << " DICOM server has stopped"; | |
1126 | |
1127 serverFactory.Done(); | |
1128 | |
1129 if (error != ErrorCode_Success) | |
1130 { | |
1131 throw OrthancException(error); | |
1132 } | |
1133 | |
1134 return restart; | |
1135 } | |
1136 } | |
1137 | |
1138 | |
1139 static bool ConfigureHttpHandler(ServerContext& context, | |
1140 OrthancPlugins *plugins, | |
1141 bool loadJobsFromDatabase) | |
1142 { | |
1143 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1144 // By order of priority, first apply the "plugins" layer, so that | |
1145 // plugins can overwrite the built-in REST API of Orthanc | |
1146 if (plugins) | |
1147 { | |
1148 assert(context.HasPlugins()); | |
1149 context.GetHttpHandler().Register(*plugins, false); | |
1150 } | |
1151 #endif | |
1152 | |
1153 // Secondly, apply the "static resources" layer | |
1154 #if ORTHANC_STANDALONE == 1 | |
1155 EmbeddedResourceHttpHandler staticResources("/app", ServerResources::ORTHANC_EXPLORER); | |
1156 #else | |
1157 FilesystemHttpHandler staticResources("/app", ORTHANC_PATH "/OrthancExplorer"); | |
1158 #endif | |
1159 | |
1160 context.GetHttpHandler().Register(staticResources, false); | |
1161 | |
1162 // Thirdly, consider the built-in REST API of Orthanc | |
1163 OrthancRestApi restApi(context); | |
1164 context.GetHttpHandler().Register(restApi, true); | |
1165 | |
1166 context.SetupJobsEngine(false /* not running unit tests */, loadJobsFromDatabase); | |
1167 | |
1168 bool restart = StartDicomServer(context, restApi, plugins); | |
1169 | |
1170 context.Stop(); | |
1171 | |
1172 return restart; | |
1173 } | |
1174 | |
1175 | |
1176 static void UpgradeDatabase(IDatabaseWrapper& database, | |
1177 IStorageArea& storageArea) | |
1178 { | |
1179 // Upgrade the schema of the database, if needed | |
1180 unsigned int currentVersion = database.GetDatabaseVersion(); | |
1181 | |
1182 LOG(WARNING) << "Starting the upgrade of the database schema"; | |
1183 LOG(WARNING) << "Current database version: " << currentVersion; | |
1184 LOG(WARNING) << "Database version expected by Orthanc: " << ORTHANC_DATABASE_VERSION; | |
1185 | |
1186 if (currentVersion == ORTHANC_DATABASE_VERSION) | |
1187 { | |
1188 LOG(WARNING) << "No upgrade is needed, start Orthanc without the \"--upgrade\" argument"; | |
1189 return; | |
1190 } | |
1191 | |
1192 if (currentVersion > ORTHANC_DATABASE_VERSION) | |
1193 { | |
1194 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion, | |
1195 "The version of the database schema (" + | |
1196 boost::lexical_cast<std::string>(currentVersion) + | |
1197 ") is too recent for this version of Orthanc. Please upgrade Orthanc."); | |
1198 } | |
1199 | |
1200 LOG(WARNING) << "Upgrading the database from schema version " | |
1201 << currentVersion << " to " << ORTHANC_DATABASE_VERSION; | |
1202 | |
1203 try | |
1204 { | |
1205 database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); | |
1206 } | |
1207 catch (OrthancException&) | |
1208 { | |
1209 LOG(ERROR) << "Unable to run the automated upgrade, please use the replication instructions: " | |
1210 << "http://book.orthanc-server.com/users/replication.html"; | |
1211 throw; | |
1212 } | |
1213 | |
1214 // Sanity check | |
1215 currentVersion = database.GetDatabaseVersion(); | |
1216 if (ORTHANC_DATABASE_VERSION != currentVersion) | |
1217 { | |
1218 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion, | |
1219 "The database schema was not properly upgraded, it is still at version " + | |
1220 boost::lexical_cast<std::string>(currentVersion)); | |
1221 } | |
1222 else | |
1223 { | |
1224 LOG(WARNING) << "The database schema was successfully upgraded, " | |
1225 << "you can now start Orthanc without the \"--upgrade\" argument"; | |
1226 } | |
1227 } | |
1228 | |
1229 | |
1230 | |
1231 namespace | |
1232 { | |
1233 class ServerContextConfigurator : public boost::noncopyable | |
1234 { | |
1235 private: | |
1236 ServerContext& context_; | |
1237 OrthancPlugins* plugins_; | |
1238 | |
1239 public: | |
1240 ServerContextConfigurator(ServerContext& context, | |
1241 OrthancPlugins* plugins) : | |
1242 context_(context), | |
1243 plugins_(plugins) | |
1244 { | |
1245 { | |
1246 OrthancConfiguration::WriterLock lock; | |
1247 lock.GetConfiguration().SetServerIndex(context.GetIndex()); | |
1248 } | |
1249 | |
1250 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1251 if (plugins_ != NULL) | |
1252 { | |
1253 plugins_->SetServerContext(context_); | |
1254 context_.SetPlugins(*plugins_); | |
1255 } | |
1256 #endif | |
1257 } | |
1258 | |
1259 ~ServerContextConfigurator() | |
1260 { | |
1261 { | |
1262 OrthancConfiguration::WriterLock lock; | |
1263 lock.GetConfiguration().ResetServerIndex(); | |
1264 } | |
1265 | |
1266 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1267 if (plugins_ != NULL) | |
1268 { | |
1269 plugins_->ResetServerContext(); | |
1270 context_.ResetPlugins(); | |
1271 } | |
1272 #endif | |
1273 } | |
1274 }; | |
1275 } | |
1276 | |
1277 | |
1278 static bool ConfigureServerContext(IDatabaseWrapper& database, | |
1279 IStorageArea& storageArea, | |
1280 OrthancPlugins *plugins, | |
1281 bool loadJobsFromDatabase) | |
1282 { | |
1283 size_t maxCompletedJobs; | |
1284 | |
1285 { | |
1286 OrthancConfiguration::ReaderLock lock; | |
1287 | |
1288 // These configuration options must be set before creating the | |
1289 // ServerContext, otherwise the possible Lua scripts will not be | |
1290 // able to properly issue HTTP/HTTPS queries | |
1291 HttpClient::ConfigureSsl(lock.GetConfiguration().GetBooleanParameter("HttpsVerifyPeers", true), | |
1292 lock.GetConfiguration().InterpretStringParameterAsPath | |
1293 (lock.GetConfiguration().GetStringParameter("HttpsCACertificates", ""))); | |
1294 HttpClient::SetDefaultVerbose(lock.GetConfiguration().GetBooleanParameter("HttpVerbose", false)); | |
1295 | |
1296 // The value "0" below makes the class HttpClient use its default | |
1297 // value (DEFAULT_HTTP_TIMEOUT = 60 seconds in Orthanc 1.5.7) | |
1298 HttpClient::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpTimeout", 0)); | |
1299 | |
1300 HttpClient::SetDefaultProxy(lock.GetConfiguration().GetStringParameter("HttpProxy", "")); | |
1301 | |
1302 DicomAssociationParameters::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScuTimeout", 10)); | |
1303 | |
1304 maxCompletedJobs = lock.GetConfiguration().GetUnsignedIntegerParameter("JobsHistorySize", 10); | |
1305 | |
1306 if (maxCompletedJobs == 0) | |
1307 { | |
1308 LOG(WARNING) << "Setting option \"JobsHistorySize\" to zero is not recommended"; | |
1309 } | |
1310 } | |
1311 | |
1312 ServerContext context(database, storageArea, false /* not running unit tests */, maxCompletedJobs); | |
1313 | |
1314 { | |
1315 OrthancConfiguration::ReaderLock lock; | |
1316 | |
1317 context.SetCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("StorageCompression", false)); | |
1318 context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true)); | |
1319 | |
1320 // New option in Orthanc 1.4.2 | |
1321 context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); | |
1322 | |
1323 try | |
1324 { | |
1325 context.GetIndex().SetMaximumPatientCount(lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0)); | |
1326 } | |
1327 catch (...) | |
1328 { | |
1329 context.GetIndex().SetMaximumPatientCount(0); | |
1330 } | |
1331 | |
1332 try | |
1333 { | |
1334 uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0); | |
1335 context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024); | |
1336 } | |
1337 catch (...) | |
1338 { | |
1339 context.GetIndex().SetMaximumStorageSize(0); | |
1340 } | |
1341 } | |
1342 | |
1343 { | |
1344 ServerContextConfigurator configurator(context, plugins); | |
1345 | |
1346 { | |
1347 OrthancConfiguration::WriterLock lock; | |
1348 lock.GetConfiguration().LoadModalitiesAndPeers(); | |
1349 } | |
1350 | |
1351 return ConfigureHttpHandler(context, plugins, loadJobsFromDatabase); | |
1352 } | |
1353 } | |
1354 | |
1355 | |
1356 static bool ConfigureDatabase(IDatabaseWrapper& database, | |
1357 IStorageArea& storageArea, | |
1358 OrthancPlugins *plugins, | |
1359 bool upgradeDatabase, | |
1360 bool loadJobsFromDatabase) | |
1361 { | |
1362 database.Open(); | |
1363 | |
1364 unsigned int currentVersion = database.GetDatabaseVersion(); | |
1365 | |
1366 if (upgradeDatabase) | |
1367 { | |
1368 UpgradeDatabase(database, storageArea); | |
1369 return false; // Stop and don't restart Orthanc (cf. issue 29) | |
1370 } | |
1371 else if (currentVersion != ORTHANC_DATABASE_VERSION) | |
1372 { | |
1373 throw OrthancException(ErrorCode_IncompatibleDatabaseVersion, | |
1374 "The database schema must be upgraded from version " + | |
1375 boost::lexical_cast<std::string>(currentVersion) + " to " + | |
1376 boost::lexical_cast<std::string>(ORTHANC_DATABASE_VERSION) + | |
1377 ": Please run Orthanc with the \"--upgrade\" argument"); | |
1378 } | |
1379 | |
1380 bool success = ConfigureServerContext | |
1381 (database, storageArea, plugins, loadJobsFromDatabase); | |
1382 | |
1383 database.Close(); | |
1384 | |
1385 return success; | |
1386 } | |
1387 | |
1388 | |
1389 static bool ConfigurePlugins(int argc, | |
1390 char* argv[], | |
1391 bool upgradeDatabase, | |
1392 bool loadJobsFromDatabase) | |
1393 { | |
1394 std::unique_ptr<IDatabaseWrapper> databasePtr; | |
1395 std::unique_ptr<IStorageArea> storage; | |
1396 | |
1397 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1398 OrthancPlugins plugins; | |
1399 plugins.SetCommandLineArguments(argc, argv); | |
1400 LoadPlugins(plugins); | |
1401 | |
1402 IDatabaseWrapper* database = NULL; | |
1403 if (plugins.HasDatabaseBackend()) | |
1404 { | |
1405 LOG(WARNING) << "Using a custom database from plugins"; | |
1406 database = &plugins.GetDatabaseBackend(); | |
1407 } | |
1408 else | |
1409 { | |
1410 databasePtr.reset(CreateDatabaseWrapper()); | |
1411 database = databasePtr.get(); | |
1412 } | |
1413 | |
1414 if (plugins.HasStorageArea()) | |
1415 { | |
1416 LOG(WARNING) << "Using a custom storage area from plugins"; | |
1417 storage.reset(plugins.CreateStorageArea()); | |
1418 } | |
1419 else | |
1420 { | |
1421 storage.reset(CreateStorageArea()); | |
1422 } | |
1423 | |
1424 assert(database != NULL); | |
1425 assert(storage.get() != NULL); | |
1426 | |
1427 return ConfigureDatabase(*database, *storage, &plugins, | |
1428 upgradeDatabase, loadJobsFromDatabase); | |
1429 | |
1430 #elif ORTHANC_ENABLE_PLUGINS == 0 | |
1431 // The plugins are disabled | |
1432 | |
1433 databasePtr.reset(CreateDatabaseWrapper()); | |
1434 storage.reset(CreateStorageArea()); | |
1435 | |
1436 assert(databasePtr.get() != NULL); | |
1437 assert(storage.get() != NULL); | |
1438 | |
1439 return ConfigureDatabase(*databasePtr, *storage, NULL, | |
1440 upgradeDatabase, loadJobsFromDatabase); | |
1441 | |
1442 #else | |
1443 # error The macro ORTHANC_ENABLE_PLUGINS must be set to 0 or 1 | |
1444 #endif | |
1445 } | |
1446 | |
1447 | |
1448 static bool StartOrthanc(int argc, | |
1449 char* argv[], | |
1450 bool upgradeDatabase, | |
1451 bool loadJobsFromDatabase) | |
1452 { | |
1453 return ConfigurePlugins(argc, argv, upgradeDatabase, loadJobsFromDatabase); | |
1454 } | |
1455 | |
1456 | |
1457 static bool DisplayPerformanceWarning() | |
1458 { | |
1459 (void) DisplayPerformanceWarning; // Disable warning about unused function | |
1460 LOG(WARNING) << "Performance warning: Non-release build, runtime debug assertions are turned on"; | |
1461 return true; | |
1462 } | |
1463 | |
1464 | |
1465 int main(int argc, char* argv[]) | |
1466 { | |
1467 Logging::Initialize(); | |
1468 | |
1469 bool upgradeDatabase = false; | |
1470 bool loadJobsFromDatabase = true; | |
1471 const char* configurationFile = NULL; | |
1472 | |
1473 | |
1474 /** | |
1475 * Parse the command-line options. | |
1476 **/ | |
1477 | |
1478 for (int i = 1; i < argc; i++) | |
1479 { | |
1480 std::string argument(argv[i]); | |
1481 | |
1482 if (argument.empty()) | |
1483 { | |
1484 // Ignore empty arguments | |
1485 } | |
1486 else if (argument[0] != '-') | |
1487 { | |
1488 if (configurationFile != NULL) | |
1489 { | |
1490 LOG(ERROR) << "More than one configuration path were provided on the command line, aborting"; | |
1491 return -1; | |
1492 } | |
1493 else | |
1494 { | |
1495 // Use the first argument that does not start with a "-" as | |
1496 // the configuration file | |
1497 | |
1498 // TODO WHAT IS THE ENCODING? | |
1499 configurationFile = argv[i]; | |
1500 } | |
1501 } | |
1502 else if (argument == "--errors") | |
1503 { | |
1504 PrintErrors(argv[0]); | |
1505 return 0; | |
1506 } | |
1507 else if (argument == "--help") | |
1508 { | |
1509 PrintHelp(argv[0]); | |
1510 return 0; | |
1511 } | |
1512 else if (argument == "--version") | |
1513 { | |
1514 PrintVersion(argv[0]); | |
1515 return 0; | |
1516 } | |
1517 else if (argument == "--verbose") | |
1518 { | |
1519 Logging::EnableInfoLevel(true); | |
1520 } | |
1521 else if (argument == "--trace") | |
1522 { | |
1523 Logging::EnableTraceLevel(true); | |
1524 } | |
1525 else if (boost::starts_with(argument, "--logdir=")) | |
1526 { | |
1527 // TODO WHAT IS THE ENCODING? | |
1528 std::string directory = argument.substr(9); | |
1529 | |
1530 try | |
1531 { | |
1532 Logging::SetTargetFolder(directory); | |
1533 } | |
1534 catch (OrthancException&) | |
1535 { | |
1536 LOG(ERROR) << "The directory where to store the log files (" | |
1537 << directory << ") is inexistent, aborting."; | |
1538 return -1; | |
1539 } | |
1540 } | |
1541 else if (boost::starts_with(argument, "--logfile=")) | |
1542 { | |
1543 // TODO WHAT IS THE ENCODING? | |
1544 std::string file = argument.substr(10); | |
1545 | |
1546 try | |
1547 { | |
1548 Logging::SetTargetFile(file); | |
1549 } | |
1550 catch (OrthancException&) | |
1551 { | |
1552 LOG(ERROR) << "Cannot write to the specified log file (" | |
1553 << file << "), aborting."; | |
1554 return -1; | |
1555 } | |
1556 } | |
1557 else if (argument == "--upgrade") | |
1558 { | |
1559 upgradeDatabase = true; | |
1560 } | |
1561 else if (argument == "--no-jobs") | |
1562 { | |
1563 loadJobsFromDatabase = false; | |
1564 } | |
1565 else if (boost::starts_with(argument, "--config=")) | |
1566 { | |
1567 // TODO WHAT IS THE ENCODING? | |
1568 std::string configurationSample; | |
1569 GetFileResource(configurationSample, ServerResources::CONFIGURATION_SAMPLE); | |
1570 | |
1571 #if defined(_WIN32) | |
1572 // Replace UNIX newlines with DOS newlines | |
1573 boost::replace_all(configurationSample, "\n", "\r\n"); | |
1574 #endif | |
1575 | |
1576 std::string target = argument.substr(9); | |
1577 | |
1578 try | |
1579 { | |
1580 if (target == "-") | |
1581 { | |
1582 // New in 1.5.8: Print to stdout | |
1583 std::cout << configurationSample; | |
1584 } | |
1585 else | |
1586 { | |
1587 SystemToolbox::WriteFile(configurationSample, target); | |
1588 } | |
1589 return 0; | |
1590 } | |
1591 catch (OrthancException&) | |
1592 { | |
1593 LOG(ERROR) << "Cannot write sample configuration as file \"" << target << "\""; | |
1594 return -1; | |
1595 } | |
1596 } | |
1597 else | |
1598 { | |
1599 LOG(WARNING) << "Option unsupported by the core of Orthanc: " << argument; | |
1600 } | |
1601 } | |
1602 | |
1603 | |
1604 /** | |
1605 * Launch Orthanc. | |
1606 **/ | |
1607 | |
1608 { | |
1609 std::string version(ORTHANC_VERSION); | |
1610 | |
1611 if (std::string(ORTHANC_VERSION) == "mainline") | |
1612 { | |
1613 try | |
1614 { | |
1615 boost::filesystem::path exe(SystemToolbox::GetPathToExecutable()); | |
1616 std::time_t creation = boost::filesystem::last_write_time(exe); | |
1617 boost::posix_time::ptime converted(boost::posix_time::from_time_t(creation)); | |
1618 version += " (" + boost::posix_time::to_iso_string(converted) + ")"; | |
1619 } | |
1620 catch (...) | |
1621 { | |
1622 } | |
1623 } | |
1624 | |
1625 LOG(WARNING) << "Orthanc version: " << version; | |
1626 assert(DisplayPerformanceWarning()); | |
1627 | |
1628 std::string s = "Architecture: "; | |
1629 if (sizeof(void*) == 4) | |
1630 { | |
1631 s += "32-bit, "; | |
1632 } | |
1633 else if (sizeof(void*) == 8) | |
1634 { | |
1635 s += "64-bit, "; | |
1636 } | |
1637 else | |
1638 { | |
1639 s += "unsupported pointer size, "; | |
1640 } | |
1641 | |
1642 switch (Toolbox::DetectEndianness()) | |
1643 { | |
1644 case Endianness_Little: | |
1645 s += "little endian"; | |
1646 break; | |
1647 | |
1648 case Endianness_Big: | |
1649 s += "big endian"; | |
1650 break; | |
1651 | |
1652 default: | |
1653 s += "unsupported endianness"; | |
1654 break; | |
1655 } | |
1656 | |
1657 LOG(INFO) << s; | |
1658 } | |
1659 | |
1660 int status = 0; | |
1661 try | |
1662 { | |
1663 for (;;) | |
1664 { | |
1665 OrthancInitialize(configurationFile); | |
1666 | |
1667 bool restart = StartOrthanc(argc, argv, upgradeDatabase, loadJobsFromDatabase); | |
1668 if (restart) | |
1669 { | |
1670 OrthancFinalize(); | |
1671 LOG(WARNING) << "Logging system is resetting"; | |
1672 Logging::Reset(); | |
1673 } | |
1674 else | |
1675 { | |
1676 break; | |
1677 } | |
1678 } | |
1679 } | |
1680 catch (const OrthancException& e) | |
1681 { | |
1682 LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "] (code " << e.GetErrorCode() << ")"; | |
1683 #if defined(_WIN32) | |
1684 if (e.GetErrorCode() >= ErrorCode_START_PLUGINS) | |
1685 { | |
1686 status = static_cast<int>(ErrorCode_Plugin); | |
1687 } | |
1688 else | |
1689 { | |
1690 status = static_cast<int>(e.GetErrorCode()); | |
1691 } | |
1692 | |
1693 #else | |
1694 status = -1; | |
1695 #endif | |
1696 } | |
1697 catch (const std::exception& e) | |
1698 { | |
1699 LOG(ERROR) << "Uncaught exception, stopping now: [" << e.what() << "]"; | |
1700 status = -1; | |
1701 } | |
1702 catch (const std::string& s) | |
1703 { | |
1704 LOG(ERROR) << "Uncaught exception, stopping now: [" << s << "]"; | |
1705 status = -1; | |
1706 } | |
1707 catch (...) | |
1708 { | |
1709 LOG(ERROR) << "Native exception, stopping now. Check your plugins, if any."; | |
1710 status = -1; | |
1711 } | |
1712 | |
1713 LOG(WARNING) << "Orthanc has stopped"; | |
1714 | |
1715 OrthancFinalize(); | |
1716 | |
1717 return status; | |
1718 } |