Mercurial > hg > orthanc
comparison OrthancServer/OrthancConfiguration.cpp @ 2933:4a38d7d4f0e0
new class: OrthancConfiguration
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 27 Nov 2018 17:08:48 +0100 |
parents | |
children | 4767d36679ed |
comparison
equal
deleted
inserted
replaced
2931:89f2c302fc37 | 2933:4a38d7d4f0e0 |
---|---|
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-2018 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 "OrthancConfiguration.h" | |
36 | |
37 #include "../Core/HttpServer/MongooseServer.h" | |
38 #include "../Core/Logging.h" | |
39 #include "../Core/OrthancException.h" | |
40 #include "../Core/SystemToolbox.h" | |
41 #include "../Core/Toolbox.h" | |
42 | |
43 namespace Orthanc | |
44 { | |
45 static void AddFileToConfiguration(Json::Value& target, | |
46 const boost::filesystem::path& path) | |
47 { | |
48 std::map<std::string, std::string> env; | |
49 SystemToolbox::GetEnvironmentVariables(env); | |
50 | |
51 LOG(WARNING) << "Reading the configuration from: " << path; | |
52 | |
53 Json::Value config; | |
54 | |
55 { | |
56 std::string content; | |
57 SystemToolbox::ReadFile(content, path.string()); | |
58 | |
59 content = Toolbox::SubstituteVariables(content, env); | |
60 | |
61 Json::Value tmp; | |
62 Json::Reader reader; | |
63 if (!reader.parse(content, tmp) || | |
64 tmp.type() != Json::objectValue) | |
65 { | |
66 LOG(ERROR) << "The configuration file does not follow the JSON syntax: " << path; | |
67 throw OrthancException(ErrorCode_BadJson); | |
68 } | |
69 | |
70 Toolbox::CopyJsonWithoutComments(config, tmp); | |
71 } | |
72 | |
73 if (target.size() == 0) | |
74 { | |
75 target = config; | |
76 } | |
77 else | |
78 { | |
79 // Merge the newly-added file with the previous content of "target" | |
80 Json::Value::Members members = config.getMemberNames(); | |
81 for (Json::Value::ArrayIndex i = 0; i < members.size(); i++) | |
82 { | |
83 if (target.isMember(members[i])) | |
84 { | |
85 LOG(ERROR) << "The configuration section \"" << members[i] << "\" is defined in 2 different configuration files"; | |
86 throw OrthancException(ErrorCode_BadFileFormat); | |
87 } | |
88 else | |
89 { | |
90 target[members[i]] = config[members[i]]; | |
91 } | |
92 } | |
93 } | |
94 } | |
95 | |
96 | |
97 static void ScanFolderForConfiguration(Json::Value& target, | |
98 const char* folder) | |
99 { | |
100 using namespace boost::filesystem; | |
101 | |
102 LOG(WARNING) << "Scanning folder \"" << folder << "\" for configuration files"; | |
103 | |
104 directory_iterator end_it; // default construction yields past-the-end | |
105 for (directory_iterator it(folder); | |
106 it != end_it; | |
107 ++it) | |
108 { | |
109 if (!is_directory(it->status())) | |
110 { | |
111 std::string extension = boost::filesystem::extension(it->path()); | |
112 Toolbox::ToLowerCase(extension); | |
113 | |
114 if (extension == ".json") | |
115 { | |
116 AddFileToConfiguration(target, it->path().string()); | |
117 } | |
118 } | |
119 } | |
120 } | |
121 | |
122 | |
123 static void ReadConfiguration(Json::Value& target, | |
124 const char* configurationFile) | |
125 { | |
126 target = Json::objectValue; | |
127 | |
128 if (configurationFile) | |
129 { | |
130 if (!boost::filesystem::exists(configurationFile)) | |
131 { | |
132 LOG(ERROR) << "Inexistent path to configuration: " << configurationFile; | |
133 throw OrthancException(ErrorCode_InexistentFile); | |
134 } | |
135 | |
136 if (boost::filesystem::is_directory(configurationFile)) | |
137 { | |
138 ScanFolderForConfiguration(target, configurationFile); | |
139 } | |
140 else | |
141 { | |
142 AddFileToConfiguration(target, configurationFile); | |
143 } | |
144 } | |
145 else | |
146 { | |
147 #if ORTHANC_STANDALONE == 1 | |
148 // No default path for the standalone configuration | |
149 LOG(WARNING) << "Using the default Orthanc configuration"; | |
150 return; | |
151 | |
152 #else | |
153 // In a non-standalone build, we use the | |
154 // "Resources/Configuration.json" from the Orthanc source code | |
155 | |
156 boost::filesystem::path p = ORTHANC_PATH; | |
157 p /= "Resources"; | |
158 p /= "Configuration.json"; | |
159 | |
160 AddFileToConfiguration(target, p); | |
161 #endif | |
162 } | |
163 } | |
164 | |
165 | |
166 void OrthancConfiguration::ValidateConfiguration() const | |
167 { | |
168 std::set<std::string> ids; | |
169 | |
170 GetListOfOrthancPeers(ids); | |
171 for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it) | |
172 { | |
173 WebServiceParameters peer; | |
174 GetOrthancPeer(peer, *it); | |
175 peer.CheckClientCertificate(); | |
176 } | |
177 | |
178 GetListOfDicomModalities(ids); | |
179 for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it) | |
180 { | |
181 RemoteModalityParameters modality; | |
182 GetDicomModalityUsingSymbolicName(modality, *it); | |
183 } | |
184 } | |
185 | |
186 | |
187 OrthancConfiguration& OrthancConfiguration::GetInstance() | |
188 { | |
189 static OrthancConfiguration configuration; | |
190 return configuration; | |
191 } | |
192 | |
193 | |
194 std::string OrthancConfiguration::GetStringParameter(const std::string& parameter, | |
195 const std::string& defaultValue) const | |
196 { | |
197 if (json_.isMember(parameter)) | |
198 { | |
199 if (json_[parameter].type() != Json::stringValue) | |
200 { | |
201 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a string"; | |
202 throw OrthancException(ErrorCode_BadParameterType); | |
203 } | |
204 else | |
205 { | |
206 return json_[parameter].asString(); | |
207 } | |
208 } | |
209 else | |
210 { | |
211 return defaultValue; | |
212 } | |
213 } | |
214 | |
215 | |
216 int OrthancConfiguration::GetIntegerParameter(const std::string& parameter, | |
217 int defaultValue) const | |
218 { | |
219 if (json_.isMember(parameter)) | |
220 { | |
221 if (json_[parameter].type() != Json::intValue) | |
222 { | |
223 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be an integer"; | |
224 throw OrthancException(ErrorCode_BadParameterType); | |
225 } | |
226 else | |
227 { | |
228 return json_[parameter].asInt(); | |
229 } | |
230 } | |
231 else | |
232 { | |
233 return defaultValue; | |
234 } | |
235 } | |
236 | |
237 | |
238 unsigned int OrthancConfiguration::GetUnsignedIntegerParameter( | |
239 const std::string& parameter, | |
240 unsigned int defaultValue) const | |
241 { | |
242 int v = GetIntegerParameter(parameter, defaultValue); | |
243 | |
244 if (v < 0) | |
245 { | |
246 LOG(ERROR) << "The configuration option \"" << parameter << "\" must be a positive integer"; | |
247 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
248 } | |
249 else | |
250 { | |
251 return static_cast<unsigned int>(v); | |
252 } | |
253 } | |
254 | |
255 | |
256 bool OrthancConfiguration::GetBooleanParameter(const std::string& parameter, | |
257 bool defaultValue) const | |
258 { | |
259 if (json_.isMember(parameter)) | |
260 { | |
261 if (json_[parameter].type() != Json::booleanValue) | |
262 { | |
263 LOG(ERROR) << "The configuration option \"" << parameter | |
264 << "\" must be a Boolean (true or false)"; | |
265 throw OrthancException(ErrorCode_BadParameterType); | |
266 } | |
267 else | |
268 { | |
269 return json_[parameter].asBool(); | |
270 } | |
271 } | |
272 else | |
273 { | |
274 return defaultValue; | |
275 } | |
276 } | |
277 | |
278 | |
279 void OrthancConfiguration::Read(const char* configurationFile) | |
280 { | |
281 // Read the content of the configuration | |
282 configurationFileArg_ = configurationFile; | |
283 ReadConfiguration(json_, configurationFile); | |
284 | |
285 // Adapt the paths to the configurations | |
286 defaultDirectory_ = boost::filesystem::current_path(); | |
287 configurationAbsolutePath_ = ""; | |
288 | |
289 if (configurationFile) | |
290 { | |
291 if (boost::filesystem::is_directory(configurationFile)) | |
292 { | |
293 defaultDirectory_ = boost::filesystem::path(configurationFile); | |
294 configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string(); | |
295 } | |
296 else | |
297 { | |
298 defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path(); | |
299 configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string(); | |
300 } | |
301 } | |
302 else | |
303 { | |
304 #if ORTHANC_STANDALONE != 1 | |
305 // In a non-standalone build, we use the | |
306 // "Resources/Configuration.json" from the Orthanc source code | |
307 | |
308 boost::filesystem::path p = ORTHANC_PATH; | |
309 p /= "Resources"; | |
310 p /= "Configuration.json"; | |
311 configurationAbsolutePath_ = boost::filesystem::absolute(p).string(); | |
312 #endif | |
313 } | |
314 | |
315 ValidateConfiguration(); | |
316 } | |
317 | |
318 | |
319 void OrthancConfiguration::GetDicomModalityUsingSymbolicName( | |
320 RemoteModalityParameters& modality, | |
321 const std::string& name) const | |
322 { | |
323 if (!json_.isMember("DicomModalities")) | |
324 { | |
325 LOG(ERROR) << "No modality with symbolic name: " << name; | |
326 throw OrthancException(ErrorCode_InexistentItem); | |
327 } | |
328 | |
329 const Json::Value& modalities = json_["DicomModalities"]; | |
330 if (modalities.type() != Json::objectValue || | |
331 !modalities.isMember(name)) | |
332 { | |
333 LOG(ERROR) << "No modality with symbolic name: " << name; | |
334 throw OrthancException(ErrorCode_InexistentItem); | |
335 } | |
336 | |
337 try | |
338 { | |
339 modality.Unserialize(modalities[name]); | |
340 } | |
341 catch (OrthancException&) | |
342 { | |
343 LOG(ERROR) << "Syntax error in the definition of DICOM modality \"" << name | |
344 << "\". Please check your configuration file."; | |
345 throw; | |
346 } | |
347 } | |
348 | |
349 | |
350 bool OrthancConfiguration::GetOrthancPeer(WebServiceParameters& peer, | |
351 const std::string& name) const | |
352 { | |
353 if (!json_.isMember("OrthancPeers")) | |
354 { | |
355 return false; | |
356 } | |
357 | |
358 try | |
359 { | |
360 const Json::Value& modalities = json_["OrthancPeers"]; | |
361 if (modalities.type() != Json::objectValue || | |
362 !modalities.isMember(name)) | |
363 { | |
364 return false; | |
365 } | |
366 else | |
367 { | |
368 peer.Unserialize(modalities[name]); | |
369 return true; | |
370 } | |
371 } | |
372 catch (OrthancException&) | |
373 { | |
374 LOG(ERROR) << "Syntax error in the definition of peer \"" << name | |
375 << "\". Please check your configuration file."; | |
376 throw; | |
377 } | |
378 } | |
379 | |
380 | |
381 bool OrthancConfiguration::ReadKeys(std::set<std::string>& target, | |
382 const char* parameter, | |
383 bool onlyAlphanumeric) const | |
384 { | |
385 target.clear(); | |
386 | |
387 if (!json_.isMember(parameter)) | |
388 { | |
389 return true; | |
390 } | |
391 | |
392 const Json::Value& modalities = json_[parameter]; | |
393 if (modalities.type() != Json::objectValue) | |
394 { | |
395 LOG(ERROR) << "Bad format of the \"DicomModalities\" configuration section"; | |
396 throw OrthancException(ErrorCode_BadFileFormat); | |
397 } | |
398 | |
399 Json::Value::Members members = modalities.getMemberNames(); | |
400 for (size_t i = 0; i < members.size(); i++) | |
401 { | |
402 if (onlyAlphanumeric) | |
403 { | |
404 for (size_t j = 0; j < members[i].size(); j++) | |
405 { | |
406 if (!isalnum(members[i][j]) && members[i][j] != '-') | |
407 { | |
408 return false; | |
409 } | |
410 } | |
411 } | |
412 | |
413 target.insert(members[i]); | |
414 } | |
415 | |
416 return true; | |
417 } | |
418 | |
419 | |
420 void OrthancConfiguration::GetListOfDicomModalities(std::set<std::string>& target) const | |
421 { | |
422 target.clear(); | |
423 | |
424 if (!ReadKeys(target, "DicomModalities", true)) | |
425 { | |
426 LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of the modalities"; | |
427 throw OrthancException(ErrorCode_BadFileFormat); | |
428 } | |
429 } | |
430 | |
431 | |
432 void OrthancConfiguration::GetListOfOrthancPeers(std::set<std::string>& target) const | |
433 { | |
434 target.clear(); | |
435 | |
436 if (!ReadKeys(target, "OrthancPeers", true)) | |
437 { | |
438 LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of Orthanc peers"; | |
439 throw OrthancException(ErrorCode_BadFileFormat); | |
440 } | |
441 } | |
442 | |
443 | |
444 void OrthancConfiguration::SetupRegisteredUsers(MongooseServer& httpServer) const | |
445 { | |
446 httpServer.ClearUsers(); | |
447 | |
448 if (!json_.isMember("RegisteredUsers")) | |
449 { | |
450 return; | |
451 } | |
452 | |
453 const Json::Value& users = json_["RegisteredUsers"]; | |
454 if (users.type() != Json::objectValue) | |
455 { | |
456 LOG(ERROR) << "Badly formatted list of users"; | |
457 throw OrthancException(ErrorCode_BadFileFormat); | |
458 } | |
459 | |
460 Json::Value::Members usernames = users.getMemberNames(); | |
461 for (size_t i = 0; i < usernames.size(); i++) | |
462 { | |
463 const std::string& username = usernames[i]; | |
464 std::string password = users[username].asString(); | |
465 httpServer.RegisterUser(username.c_str(), password.c_str()); | |
466 } | |
467 } | |
468 | |
469 | |
470 std::string OrthancConfiguration::InterpretStringParameterAsPath( | |
471 const std::string& parameter) const | |
472 { | |
473 return SystemToolbox::InterpretRelativePath(defaultDirectory_.string(), parameter); | |
474 } | |
475 | |
476 | |
477 void OrthancConfiguration::GetGlobalListOfStringsParameter( | |
478 std::list<std::string>& target, | |
479 const std::string& key) const | |
480 { | |
481 target.clear(); | |
482 | |
483 if (!json_.isMember(key)) | |
484 { | |
485 return; | |
486 } | |
487 | |
488 const Json::Value& lst = json_[key]; | |
489 | |
490 if (lst.type() != Json::arrayValue) | |
491 { | |
492 LOG(ERROR) << "Badly formatted list of strings"; | |
493 throw OrthancException(ErrorCode_BadFileFormat); | |
494 } | |
495 | |
496 for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++) | |
497 { | |
498 target.push_back(lst[i].asString()); | |
499 } | |
500 } | |
501 | |
502 | |
503 bool OrthancConfiguration::IsSameAETitle(const std::string& aet1, | |
504 const std::string& aet2) const | |
505 { | |
506 if (GetBooleanParameter("StrictAetComparison", false)) | |
507 { | |
508 // Case-sensitive matching | |
509 return aet1 == aet2; | |
510 } | |
511 else | |
512 { | |
513 // Case-insensitive matching (default) | |
514 std::string tmp1, tmp2; | |
515 Toolbox::ToLowerCase(tmp1, aet1); | |
516 Toolbox::ToLowerCase(tmp2, aet2); | |
517 return tmp1 == tmp2; | |
518 } | |
519 } | |
520 | |
521 | |
522 bool OrthancConfiguration::LookupDicomModalityUsingAETitle( | |
523 RemoteModalityParameters& modality, | |
524 const std::string& aet) const | |
525 { | |
526 std::set<std::string> modalities; | |
527 GetListOfDicomModalities(modalities); | |
528 | |
529 for (std::set<std::string>::const_iterator | |
530 it = modalities.begin(); it != modalities.end(); ++it) | |
531 { | |
532 try | |
533 { | |
534 GetDicomModalityUsingSymbolicName(modality, *it); | |
535 | |
536 if (IsSameAETitle(aet, modality.GetApplicationEntityTitle())) | |
537 { | |
538 return true; | |
539 } | |
540 } | |
541 catch (OrthancException&) | |
542 { | |
543 } | |
544 } | |
545 | |
546 return false; | |
547 } | |
548 | |
549 | |
550 bool OrthancConfiguration::IsKnownAETitle(const std::string& aet, | |
551 const std::string& ip) const | |
552 { | |
553 RemoteModalityParameters modality; | |
554 | |
555 if (!LookupDicomModalityUsingAETitle(modality, aet)) | |
556 { | |
557 LOG(WARNING) << "Modality \"" << aet | |
558 << "\" is not listed in the \"DicomModalities\" configuration option"; | |
559 return false; | |
560 } | |
561 else if (!GetBooleanParameter("DicomCheckModalityHost", false) || | |
562 ip == modality.GetHost()) | |
563 { | |
564 return true; | |
565 } | |
566 else | |
567 { | |
568 LOG(WARNING) << "Forbidding access from AET \"" << aet | |
569 << "\" given its hostname (" << ip << ") does not match " | |
570 << "the \"DicomModalities\" configuration option (" | |
571 << modality.GetHost() << " was expected)"; | |
572 return false; | |
573 } | |
574 } | |
575 | |
576 | |
577 RemoteModalityParameters OrthancConfiguration::GetModalityUsingSymbolicName( | |
578 const std::string& name) const | |
579 { | |
580 RemoteModalityParameters modality; | |
581 GetDicomModalityUsingSymbolicName(modality, name); | |
582 | |
583 return modality; | |
584 } | |
585 | |
586 | |
587 RemoteModalityParameters OrthancConfiguration::GetModalityUsingAet( | |
588 const std::string& aet) const | |
589 { | |
590 RemoteModalityParameters modality; | |
591 | |
592 if (LookupDicomModalityUsingAETitle(modality, aet)) | |
593 { | |
594 return modality; | |
595 } | |
596 else | |
597 { | |
598 LOG(ERROR) << "Unknown modality for AET: " << aet; | |
599 throw OrthancException(ErrorCode_InexistentItem); | |
600 } | |
601 } | |
602 | |
603 | |
604 void OrthancConfiguration::UpdateModality(const std::string& symbolicName, | |
605 const RemoteModalityParameters& modality) | |
606 { | |
607 if (!json_.isMember("DicomModalities")) | |
608 { | |
609 json_["DicomModalities"] = Json::objectValue; | |
610 } | |
611 | |
612 Json::Value& modalities = json_["DicomModalities"]; | |
613 if (modalities.type() != Json::objectValue) | |
614 { | |
615 LOG(ERROR) << "Bad file format for modality: " << symbolicName; | |
616 throw OrthancException(ErrorCode_BadFileFormat); | |
617 } | |
618 | |
619 modalities.removeMember(symbolicName); | |
620 | |
621 Json::Value v; | |
622 modality.Serialize(v, true /* force advanced format */); | |
623 modalities[symbolicName] = v; | |
624 } | |
625 | |
626 | |
627 void OrthancConfiguration::RemoveModality(const std::string& symbolicName) | |
628 { | |
629 if (!json_.isMember("DicomModalities")) | |
630 { | |
631 LOG(ERROR) << "No modality with symbolic name: " << symbolicName; | |
632 throw OrthancException(ErrorCode_BadFileFormat); | |
633 } | |
634 | |
635 Json::Value& modalities = json_["DicomModalities"]; | |
636 if (modalities.type() != Json::objectValue) | |
637 { | |
638 LOG(ERROR) << "Bad file format for the \"DicomModalities\" configuration section"; | |
639 throw OrthancException(ErrorCode_BadFileFormat); | |
640 } | |
641 | |
642 modalities.removeMember(symbolicName.c_str()); | |
643 } | |
644 | |
645 | |
646 void OrthancConfiguration::UpdatePeer(const std::string& symbolicName, | |
647 const WebServiceParameters& peer) | |
648 { | |
649 peer.CheckClientCertificate(); | |
650 | |
651 if (!json_.isMember("OrthancPeers")) | |
652 { | |
653 LOG(ERROR) << "No peer with symbolic name: " << symbolicName; | |
654 json_["OrthancPeers"] = Json::objectValue; | |
655 } | |
656 | |
657 Json::Value& peers = json_["OrthancPeers"]; | |
658 if (peers.type() != Json::objectValue) | |
659 { | |
660 LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; | |
661 throw OrthancException(ErrorCode_BadFileFormat); | |
662 } | |
663 | |
664 peers.removeMember(symbolicName); | |
665 | |
666 Json::Value v; | |
667 peer.Serialize(v, | |
668 false /* use simple format if possible */, | |
669 true /* include passwords */); | |
670 peers[symbolicName] = v; | |
671 } | |
672 | |
673 | |
674 void OrthancConfiguration::RemovePeer(const std::string& symbolicName) | |
675 { | |
676 if (!json_.isMember("OrthancPeers")) | |
677 { | |
678 LOG(ERROR) << "No peer with symbolic name: " << symbolicName; | |
679 throw OrthancException(ErrorCode_BadFileFormat); | |
680 } | |
681 | |
682 Json::Value& peers = json_["OrthancPeers"]; | |
683 if (peers.type() != Json::objectValue) | |
684 { | |
685 LOG(ERROR) << "Bad file format for the \"OrthancPeers\" configuration section"; | |
686 throw OrthancException(ErrorCode_BadFileFormat); | |
687 } | |
688 | |
689 peers.removeMember(symbolicName.c_str()); | |
690 } | |
691 | |
692 | |
693 void OrthancConfiguration::Format(std::string& result) const | |
694 { | |
695 Json::StyledWriter w; | |
696 result = w.write(json_); | |
697 } | |
698 | |
699 | |
700 void OrthancConfiguration::SetDefaultEncoding(Encoding encoding) | |
701 { | |
702 SetDefaultDicomEncoding(encoding); | |
703 | |
704 // Propagate the encoding to the configuration file that is | |
705 // stored in memory | |
706 json_["DefaultEncoding"] = EnumerationToString(encoding); | |
707 } | |
708 | |
709 | |
710 bool OrthancConfiguration::HasConfigurationChanged() const | |
711 { | |
712 Json::Value current; | |
713 ReadConfiguration(current, configurationFileArg_); | |
714 | |
715 Json::FastWriter writer; | |
716 std::string a = writer.write(json_); | |
717 std::string b = writer.write(current); | |
718 | |
719 return a != b; | |
720 } | |
721 } |