Mantid
Loading...
Searching...
No Matches
ConfigService.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
10
15#include "MantidKernel/Glob.h"
16#include "MantidKernel/Logger.h"
22
23#include <Poco/AutoPtr.h>
24#include <Poco/Channel.h>
25#include <Poco/DOM/DOMParser.h>
26#include <Poco/DOM/Document.h>
27#include <Poco/DOM/Element.h>
28#include <Poco/DOM/Node.h>
29#include <Poco/DOM/NodeList.h>
30#include <Poco/Environment.h>
31#include <Poco/Exception.h>
32#include <Poco/Instantiator.h>
33#include <Poco/Logger.h>
34#include <Poco/LoggingFactory.h>
35#include <Poco/LoggingRegistry.h>
36#include <Poco/Path.h>
37#include <Poco/Pipe.h>
38#include <Poco/PipeStream.h>
39#include <Poco/Platform.h>
40#include <Poco/Process.h>
41#include <Poco/StreamCopier.h>
42#include <Poco/String.h>
43#include <Poco/URI.h>
44#include <Poco/Util/LoggingConfigurator.h>
45#include <Poco/Util/PropertyFileConfiguration.h>
46#include <Poco/Util/SystemConfiguration.h>
47#include <Poco/Version.h>
48
49#include <boost/algorithm/string/join.hpp>
50#include <boost/algorithm/string/trim.hpp>
51#include <optional>
52
53#include <algorithm>
54#include <cctype>
55#include <codecvt>
56#include <cstdlib>
57#include <cstring>
58#include <exception>
59#include <filesystem>
60#include <fstream>
61#include <functional>
62#include <iostream>
63#include <locale>
64#include <stdexcept>
65#include <utility>
66
67#ifdef __APPLE__
68#include <mach-o/dyld.h>
69#include <sys/sysctl.h>
70#endif
71
72namespace Mantid {
77std::string welcomeMessage() {
78 return "Welcome to Mantid " + std::string(Mantid::Kernel::MantidVersion::version()) +
80 " and this release: " + Mantid::Kernel::MantidVersion::doi();
81}
82
83namespace Kernel {
84
85namespace { // anonymous namespace for some utility functions
86
88Logger g_log("ConfigService");
89
90const std::string PATH_DELIMITERS = ";,";
91
98std::vector<std::string> splitPath(const std::string &path) {
99 std::vector<std::string> splitted;
100
101 if (path.find_first_of(PATH_DELIMITERS) == std::string::npos) { // don't bother tokenizing
102 splitted.emplace_back(path);
103 } else {
105 Mantid::Kernel::StringTokenizer tokenizer(path, PATH_DELIMITERS, options);
106 auto iend = tokenizer.end();
107 for (auto itr = tokenizer.begin(); itr != iend; ++itr) {
108 if (!itr->empty()) {
109 splitted.emplace_back(*itr);
110 }
111 }
112 }
113 return splitted;
114}
115
116const std::string LOG_LEVEL_KEY("logging.loggers.root.level");
117
118} // end of anonymous namespace
119
120//-------------------------------
121// Private member functions
122//-------------------------------
123
126 : m_pConf(nullptr), m_pSysConfig(new Poco::Util::SystemConfiguration()), m_changed_keys(), m_strBaseDir(""),
127 m_propertyString(""), m_properties_file_name("Mantid.properties"),
128 m_user_properties_file_name("Mantid.user.properties"), m_dataSearchDirs(), m_instrumentDirs(), m_proxyInfo(),
129 m_isProxySet(false) {
130 // Register StdChannel with Poco
131 Poco::LoggingFactory::defaultFactory().registerChannelClass(
132 "StdoutChannel", new Poco::Instantiator<Poco::StdoutChannel, Poco::Channel>);
133
135
136 m_configPaths.insert("framework.plugins.directory");
137 m_configPaths.insert("mantidqt.plugins.directory");
138 m_configPaths.insert("instrumentDefinition.directory");
139 m_configPaths.insert("instrumentDefinition.vtpDirectory");
140 m_configPaths.insert("groupingFiles.directory");
141 m_configPaths.insert("maskFiles.directory");
142 m_configPaths.insert("colormaps.directory");
143 m_configPaths.insert("requiredpythonscript.directories");
144 m_configPaths.insert("pythonscripts.directory");
145 m_configPaths.insert("pythonscripts.directories");
146 m_configPaths.insert("python.plugins.directories");
147 m_configPaths.insert("user.python.plugins.directories");
148 m_configPaths.insert("icatDownload.directory");
149 m_configPaths.insert("datasearch.directories");
150 m_configPaths.insert("python.plugins.manifest");
151 m_configPaths.insert("python.templates.directory");
152
153 // attempt to load the default properties file that resides in the directory
154 // of the executable
155 std::string propertiesFilesList;
157 propertiesFilesList = getPropertiesDir() + m_properties_file_name;
158
159 // Load the local (machine) properties file, if it exists
160 if (std::filesystem::exists(getLocalFilename())) {
161 updateConfig(getLocalFilename(), true, false);
162 propertiesFilesList += ", " + getLocalFilename();
163 }
164
165 if (Poco::Environment::has("MANTIDPROPERTIES")) {
166 // and then append the user properties
167 updateConfig(getUserFilename(), true, false);
168 propertiesFilesList += ", " + getUserFilename();
169 // and the extra one from the environment
170 updateConfig(Poco::Environment::get("MANTIDPROPERTIES"), true, true);
171 propertiesFilesList += ", " + Poco::Environment::get("MANTIDPROPERTIES");
172 } else {
173 // Just do the user properties
174 updateConfig(getUserFilename(), true, true);
175 propertiesFilesList += ", " + getUserFilename();
176 }
177
178 g_log.debug() << "ConfigService created.\n";
179 g_log.debug() << "Configured Mantid.properties directory of application as " << getPropertiesDir() << '\n';
180 g_log.information() << "This is Mantid version " << MantidVersion::version() << " revision "
181 << MantidVersion::revision() << '\n';
182 g_log.information() << "running on " << getComputerName() << " starting "
183 << Types::Core::DateAndTime::getCurrentTime().toFormattedString("%Y-%m-%dT%H:%MZ") << "\n";
184 g_log.information() << "Properties file(s) loaded: " << propertiesFilesList << '\n';
185
186 // Assert that the appdata and the instrument subdirectory exists
187 std::string appDataDir = getAppDataDir();
188 std::filesystem::path path = std::filesystem::path(appDataDir) / "instrument";
189 // create_directories will fail gracefully if it is already present - but will
190 // throw an error if it cannot create the directory
191 try {
192 std::filesystem::create_directories(path);
193 } catch (const std::filesystem::filesystem_error &fe) {
194 g_log.error() << "Cannot create the local instrument cache directory [" << path.string()
195 << "]. Mantid will not be able to update instrument definitions.\n"
196 << fe.what() << '\n';
197 }
198 std::filesystem::path vtpDir(getVTPFileDirectory());
199 try {
200 std::filesystem::create_directories(vtpDir);
201 } catch (const std::filesystem::filesystem_error &fe) {
202 g_log.error() << "Cannot create the local instrument geometry cache directory [" << vtpDir.string()
203 << "]. Mantid will be slower at viewing complex instruments.\n"
204 << fe.what() << '\n';
205 }
206 // must update the cache of instrument paths
208
209 // update the facilities AFTER we have ensured that all of the directories are
210 // created and the paths updated
211 // if we don't do that first the function below will silently fail without
212 // initialising the facilities vector
213 // and Mantid will crash when it tries to access them, for example when
214 // creating the first time startup screen
216}
217
225
239 // Define the directory to search for the Mantid.properties file.
240 std::filesystem::path filepath;
241
242 // First directory: the current working
243 m_strBaseDir = std::filesystem::current_path().string() + "/";
244 filepath = std::filesystem::path(m_strBaseDir) / m_properties_file_name;
245 if (std::filesystem::exists(filepath))
246 return;
247
248 // Check the executable directory to see if it includes a mantid.properties
249 // file
251 filepath = std::filesystem::path(m_strBaseDir) / m_properties_file_name;
252 if (std::filesystem::exists(filepath))
253 return;
254
255 // Check the MANTIDPATH environment var
256 if (Poco::Environment::has("MANTIDPATH")) {
257 // Here we have to follow the convention of the rest of this code and
258 // add a trailing slash.
259 // Note: adding it to the MANTIDPATH itself will make other parts of the
260 // code crash.
261#ifdef _WIN32
262 // In case the path contains backslashes, we cannot
263 // mix forward and back slashes in the path.
264 m_strBaseDir = Poco::Environment::get("MANTIDPATH") + "\\";
265#else
266 m_strBaseDir = Poco::Environment::get("MANTIDPATH") + "/";
267#endif
268 filepath = std::filesystem::path(m_strBaseDir) / m_properties_file_name;
269 if (std::filesystem::exists(filepath))
270 return;
271 }
272
273#ifdef __APPLE__
274 // Finally, on OSX check if we're in the package directory and the .properties
275 // file just happens to be two directories up
276 std::filesystem::path execPath(getDirectoryOfExecutable());
277 m_strBaseDir = execPath.parent_path().parent_path().parent_path().string() + "/";
278#endif
279}
280
281namespace {
282// look for specific keys and throw an exception if one is found
283std::string checkForBadConfigOptions(const std::string &filename, const std::string &propertiesString) {
284 std::stringstream stream(propertiesString);
285 std::stringstream resultPropertiesString;
286 std::string line;
287 int line_num = 0;
288 while (std::getline(stream, line)) {
289 line_num += 1; // increment early
290 bool is_ok = true;
291
292 // Check for common errors. Empty lines are ok, things that are a key
293 // without a value are a critical failure. Forbidden keys are just commented
294 // out.
295 if (line.empty() || (Kernel::Strings::strip(line)[0] == '#')) {
296 // do nothing
297 } else if (line.find("FilterChannel") != std::string::npos) {
298 is_ok = false;
299 }
300
301 // Print warning to error channel and comment out offending line
302 if (!is_ok) {
303 const auto end = line.find("=");
304 g_log.warning() << "Encontered invalid key \"";
305 if (end != std::string::npos) {
306 g_log.warning() << Kernel::Strings::strip(line.substr(0, end));
307 } else {
309 }
310 g_log.warning() << "\" in " << filename << " on line " << line_num << std::endl;
311
312 // comment out the property
313 resultPropertiesString << '#';
314 }
315 // copy over the line
316 resultPropertiesString << line << '\n';
317 }
318 return resultPropertiesString.str();
319}
320} // end of anonymous namespace
321
331void ConfigServiceImpl::loadConfig(const std::string &filename, const bool append) {
332
333 if (!append) {
334 // remove the previous property string
335 m_propertyString = "";
336 m_changed_keys.clear();
337 }
338
339 try {
340 // slurp in entire file
341 std::string temp;
342 bool good = readFile(filename, temp);
343
344 // check if we have failed to open the file
345 if ((!good) || (temp.empty())) {
347 // write out a fresh file
349 } else {
350 throw Exception::FileError("Cannot open file", filename);
351 }
352 }
353
354 // verify the contents and comment out offending lines
355 temp = checkForBadConfigOptions(filename, temp);
356
357 // store the property string
358 if ((append) && (!m_propertyString.empty())) {
359 m_propertyString = m_propertyString + "\n" + temp;
360 } else {
361 m_propertyString = temp;
362 }
363 } catch (std::exception &e) {
364 // there was a problem loading the file - it probably is not there
365 g_log.error() << "Problem loading the configuration file " << filename << " " << e.what() << '\n';
366 g_log.error() << "Mantid is unable to start.\n" << std::endl;
367 throw;
368 }
369
370 // use the cached property string to initialise the POCO property file
371 std::istringstream istr(m_propertyString);
372 m_pConf = new Poco::Util::PropertyFileConfiguration(istr);
373}
374
381bool ConfigServiceImpl::readFile(const std::string &filename, std::string &contents) const {
382 std::ifstream propFile(filename.c_str(), std::ios::in);
383 bool good = propFile.good();
384 if (!good) {
385 contents = "";
386 propFile.close();
387 return good;
388 }
389
390 // slurp in entire file - extremely unlikely delimiter used as an alternate to
391 // \n
392 contents.clear();
393 getline(propFile, contents, '`');
394 propFile.close();
395 return good;
396}
397
402 try {
403 // Configure the logging framework
404 Poco::Util::LoggingConfigurator configurator;
405#if POCO_VERSION > 0x01090400
406 configurator.configure(m_pConf);
407#else
408 configurator.configure(m_pConf.get());
409#endif
410 } catch (std::exception &e) {
411 std::cerr << "Trouble configuring the logging framework " << e.what() << '\n';
412 }
413}
414
422std::string ConfigServiceImpl::makeAbsolute(const std::string &dir, const std::string &key) const {
423 if (dir.empty()) {
424 // Don't do anything for an empty value
425 return dir;
426 }
427 std::string converted;
428 // If we have a list, chop it up and convert each one
429 if (dir.find_first_of(PATH_DELIMITERS) != std::string::npos) {
430 auto splitted = splitPath(dir);
431 auto iend = splitted.cend();
432 for (auto itr = splitted.begin(); itr != iend;) {
433 std::string absolute = makeAbsolute(*itr, key);
434 if (absolute.empty()) {
435 ++itr;
436 } else {
437 converted += absolute;
438 if (++itr != iend) {
439 converted += ";";
440 }
441 }
442 }
443 return converted;
444 }
445
446 // MG 05/10/09: When the Poco::FilePropertyConfiguration object reads its
447 // key/value pairs it
448 // treats a backslash as the start of an escape sequence. If the next
449 // character does not
450 // form a valid sequence then the backslash is removed from the stream. This
451 // has the effect
452 // of giving malformed paths when using Windows-style directories. E.g
453 // C:\Mantid ->C:Mantid
454 // std::filesystem::path can handle this better
455 bool is_relative(false);
456 try {
457 std::filesystem::path testPath(dir);
458 is_relative = testPath.is_relative();
459
460#ifdef _WIN32
461 // On Windows, std::filesystem treats paths starting with / or \ as relative
462 // (because they lack a drive letter), but we want to treat them as absolute
463 // to match the original Poco::Path behavior and Unix conventions
464 if (is_relative && !dir.empty() && (dir[0] == '/' || dir[0] == '\\')) {
465 is_relative = false;
466 }
467#endif
468 } catch (const std::exception &) {
469 g_log.warning() << "Malformed path detected in the \"" << key << "\" variable, skipping \"" << dir << "\"\n";
470 return "";
471 }
472 if (is_relative) {
473 const std::string propFileDir(getPropertiesDir());
474 std::filesystem::path basePath(propFileDir);
475 std::filesystem::path fullPath = basePath / dir;
476 converted = fullPath.string();
477 } else {
478 converted = dir;
479 }
480 std::filesystem::path convertedPath(converted);
481 if (!convertedPath.extension().empty()) {
482 converted = convertedPath.string();
483 } else {
484 // Ensure directory has trailing slash
485 converted = convertedPath.string();
486 if (converted.back() != '/' && converted.back() != '\\') {
487 converted += "/";
488 }
489 }
490 // Backward slashes cannot be allowed to go into our properties file
491 // Note this is a temporary fix for ticket #2445.
492 // Ticket #2460 prompts a review of our path handling in the config service.
493 boost::replace_all(converted, "\\", "/");
494 return converted;
495}
496
503 std::string paths = getString("datasearch.directories", true);
504 if (paths.empty()) {
505 m_dataSearchDirs.clear();
506 } else {
507 m_dataSearchDirs = splitPath(paths);
508 }
509}
510
517bool ConfigServiceImpl::isInDataSearchList(const std::string &path) const {
518 // the path produced by poco will have \ on windows, but the searchdirs will
519 // always have /
520 std::string correctedPath = path;
521 replace(correctedPath.begin(), correctedPath.end(), '\\', '/');
522
523 using std::placeholders::_1;
524 auto it = std::find_if(m_dataSearchDirs.cbegin(), m_dataSearchDirs.cend(),
525 std::bind(std::equal_to<std::string>(), _1, correctedPath));
526 return (it != m_dataSearchDirs.end());
527}
528
534 try {
535 std::fstream filestr((getUserPropertiesDir() + m_user_properties_file_name).c_str(), std::fstream::out);
536
537 filestr << "# This file can be used to override any properties for this "
538 "installation.\n";
539 filestr << "# Any properties found in this file will override any that are "
540 "found in the Mantid.Properties file\n";
541 filestr << "# As this file will not be replaced with further installations "
542 "of Mantid it is a safe place to put \n";
543 filestr << "# properties that suit your particular installation.\n";
544 filestr << "#\n";
545 filestr << "# See here for a list of possible options:\n";
546 filestr << "# "
547 "http://docs.mantidproject.org/nightly/concepts/PropertiesFile.html"
548 "\n\n";
549 filestr << "##\n";
550 filestr << "## GENERAL\n";
551 filestr << "##\n\n";
552 filestr << "## Set the maximum number of cores used to run algorithms over\n";
553 filestr << "#MultiThreaded.MaxCores=4\n\n";
554 filestr << "##\n";
555 filestr << "## FACILITY AND INSTRUMENT\n";
556 filestr << "##\n\n";
557 filestr << "## Sets the default facility\n";
558 filestr << "## e.g.: ISIS, SNS, ILL\n";
559 filestr << "default.facility=\n\n";
560 filestr << "## Sets the default instrument\n";
561 filestr << "## e.g. IRIS, HET, NIMROD\n";
562 filestr << "default.instrument=\n\n";
563 filestr << '\n';
564 filestr << "## Sets the Q.convention\n";
565 filestr << "## Set to Crystallography for kf-ki instead of default "
566 "Inelastic which is ki-kf\n";
567 filestr << "#Q.convention=Crystallography\n";
568 filestr << "##\n";
569 filestr << "## DIRECTORIES\n";
570 filestr << "##\n\n";
571 filestr << "## Sets a list of directories (separated by semi colons) to "
572 "search for data\n";
573 filestr << "#datasearch.directories=../data;../isis/data\n\n";
574 filestr << "## Set a list (separated by semi colons) of directories to "
575 "look for additional Python scripts\n";
576 filestr << "#pythonscripts.directories=../scripts;../docs/MyScripts\n\n";
577 filestr << "## Uncomment to enable archive search - ICat and Orbiter\n";
578 filestr << "#datasearch.searcharchive=On\n\n";
579 filestr << "## Sets default save directory\n";
580 filestr << "#defaultsave.directory=../data\n\n";
581 filestr << "##\n";
582 filestr << "## LOGGING\n";
583 filestr << "##\n\n";
584 filestr << "## Uncomment to change logging level\n";
585 filestr << "## Default is information\n";
586 filestr << "## Valid values are: error, warning, notice, information, debug\n";
587 filestr << "#logging.loggers.root.level=information\n\n";
588 filestr << "##\n";
589 filestr << "## MantidWorkbench\n";
590 filestr << "##\n\n";
591 filestr << "## Hides categories from the algorithm list in MantidWorkbench\n";
592 filestr << "#algorithms.catagories.hidden=Muons,Inelastic\n\n";
593 filestr << "## Show invisible workspaces\n";
594 filestr << "#MantidOptions.InvisibleWorkspaces=0\n";
595 filestr << "## Re-use plot instances for different plot types\n";
596 filestr << "#MantidOptions.ReusePlotInstances=Off\n\n";
597 filestr << "## Uncomment to disable use of OpenGL to render unwrapped "
598 "instrument views\n";
599 filestr << "#MantidOptions.InstrumentView.UseOpenGL=Off\n\n";
600 filestr << "## Muon GUI settings\n";
601 filestr << "#muon.GUI = \n";
602
603 filestr.close();
604 } catch (std::runtime_error &ex) {
605 g_log.warning() << "Unable to write out user.properties file to " << getUserPropertiesDir()
606 << m_user_properties_file_name << " error: " << ex.what() << '\n';
607 }
608}
609
610//-------------------------------
611// Public member functions
612//-------------------------------
613
618 // Remove the current user properties file and write a fresh one
619 try {
620 std::filesystem::remove(getUserFilename());
621 } catch (const std::exception &) {
622 }
624
625 // Now load the original
626 const bool append = false;
627 const bool updateCaches = true;
628 updateConfig(getPropertiesDir() + m_properties_file_name, append, updateCaches);
629}
630
640void ConfigServiceImpl::updateConfig(const std::string &filename, const bool append, const bool update_caches) {
641 loadConfig(filename, append);
642
643 if (update_caches) {
644 // Only configure logging once
646 // Configure search paths into a specially saved store as they will be used
647 // frequently
649 appendDataSearchDir(getString("defaultsave.directory"));
651 }
652}
653
659void ConfigServiceImpl::saveConfig(const std::string &filename) const {
660 // Open and read the user properties file
661 std::string updated_file;
662
663 std::ifstream reader(filename.c_str(), std::ios::in);
664 if (reader.bad()) {
665 throw std::runtime_error("Error opening user properties file. Cannot save "
666 "updated configuration.");
667 }
668
669 std::string file_line, output;
670 bool line_continuing(false);
671 while (std::getline(reader, file_line)) {
672 if (!file_line.empty()) {
673 char last = *(file_line.end() - 1);
674 if (last == '\\') {
675 // If we are not in line continuation mode then need
676 // a fresh start line
677 if (!line_continuing)
678 output = "";
679 line_continuing = true;
680 output += file_line + "\n";
681 continue;
682 } else if (line_continuing) {
683 output += file_line;
684 line_continuing = false;
685 } else {
686 output = file_line;
687 }
688 } else {
689 output = "";
690 updated_file += "\n";
691 continue;
692 } // end if-else
693
694 // Output is the current line in the file
695
696 // Extract the key from the current line
697 std::string key;
698 std::string::size_type pos = output.find('=');
699 if (pos == std::string::npos) {
700 key = output; // If no equals then the entire thing is the key
701 } else {
702 key = output.substr(0, pos); // Strip the equals to get only the key
703 }
704 // Now deal with trimming (removes spaces)
705 Poco::trimInPlace(key);
706
707 // Find the comments
708 std::string::size_type comment = key.find('#');
709
710 // Check if it exists in the service using hasProperty and make sure it
711 // isn't a comment
712 if (comment == 0) {
713 updated_file += output;
714 } else if (!hasProperty(key)) {
715 // Remove the key from the changed key list
716 m_changed_keys.erase(key);
717 continue;
718 } else {
719 // If it does exist make sure the value is current
720 std::string value = getString(key, false);
721 Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
722 updated_file.append(key).append("=").append(value);
723 // Remove the key from the changed key list
724 m_changed_keys.erase(key);
725 }
726 updated_file += "\n";
727 } // End while-loop
728
729 // Any remaining keys within the changed key store weren't present in the
730 // current user properties so append them IF they exist
731 if (!m_changed_keys.empty()) {
732 updated_file += "\n";
733 auto key_end = m_changed_keys.end();
734 for (auto key_itr = m_changed_keys.begin(); key_itr != key_end;) {
735 // if the key does not have a property, skip it
736 if (!hasProperty(*key_itr)) {
737 ++key_itr;
738 continue;
739 }
740 updated_file += *key_itr + "=";
741 std::string value = getString(*key_itr, false);
742 Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
743 updated_file += value;
744 if (++key_itr != key_end) {
745 updated_file += "\n";
746 }
747 }
748 m_changed_keys.clear();
749 }
750
751 // Write out the new file
752 std::ofstream writer(filename.c_str(), std::ios_base::trunc);
753 if (writer.bad()) {
754 writer.close();
755 g_log.error() << "Error writing new user properties file. Cannot save "
756 "current configuration.\n";
757 throw std::runtime_error("Error writing new user properties file. Cannot "
758 "save current configuration.");
759 }
760
761 writer.write(updated_file.c_str(), updated_file.size());
762 writer.close();
763}
764
777std::string ConfigServiceImpl::getString(const std::string &keyName, bool pathAbsolute) const {
778 if (m_pConf->hasProperty(keyName)) {
779 std::string value = m_pConf->getString(keyName);
780 const auto key = m_configPaths.find(keyName);
781 if (pathAbsolute && key != m_configPaths.end()) {
782 value = makeAbsolute(value, keyName);
783 }
784 return value;
785 }
786
787 g_log.debug() << "Unable to find " << keyName << " in the properties file" << '\n';
788 return {};
789}
790
800std::vector<std::string> ConfigServiceImpl::getKeys(const std::string &keyName) const {
801 std::vector<std::string> rawKeys;
802 m_pConf->keys(keyName, rawKeys);
803 return rawKeys;
804}
805
809void ConfigServiceImpl::getKeysRecursive(const std::string &root, std::vector<std::string> &allKeys) const {
810 std::vector<std::string> rootKeys = getKeys(root);
811
812 if (rootKeys.empty())
813 allKeys.emplace_back(root);
814
815 for (auto &rootKey : rootKeys) {
816 std::string searchString;
817 if (root.empty()) {
818 searchString.append(rootKey);
819 } else {
820 searchString.append(root).append(".").append(rootKey);
821 }
822
823 getKeysRecursive(searchString, allKeys);
824 }
825}
826
835std::vector<std::string> ConfigServiceImpl::keys() const {
836 std::vector<std::string> allKeys;
837 getKeysRecursive("", allKeys);
838 return allKeys;
839}
840
849void ConfigServiceImpl::remove(const std::string &rootName) {
850 m_pConf->remove(rootName);
851 m_changed_keys.insert(rootName);
852}
853
860bool ConfigServiceImpl::hasProperty(const std::string &rootName) const { return m_pConf->hasProperty(rootName); }
861
862namespace {
865std::string expandEnvironmentInFilepath(const std::string &target) {
866 std::string result = target;
867 size_t pos = 0;
868
869#ifdef _WIN32
870 // Windows style: %VAR%
871 while ((pos = result.find('%', pos)) != std::string::npos) {
872 size_t end = result.find('%', pos + 1);
873 if (end == std::string::npos)
874 break;
875
876 std::string varName = result.substr(pos + 1, end - pos - 1);
877 const char *envValue = std::getenv(varName.c_str());
878
879 if (envValue) {
880 result.replace(pos, end - pos + 1, envValue);
881 pos += std::strlen(envValue);
882 } else {
883 pos = end + 1;
884 }
885 }
886#else
887 // Unix style: $VAR or ${VAR}
888 pos = 0;
889 while ((pos = result.find('$', pos)) != std::string::npos) {
890 size_t start = pos;
891 size_t end;
892 std::string varName;
893
894 if (pos + 1 < result.length() && result[pos + 1] == '{') {
895 // ${VAR} format
896 end = result.find('}', pos + 2);
897 if (end == std::string::npos) {
898 pos++;
899 continue;
900 }
901 varName = result.substr(pos + 2, end - pos - 2);
902 end++; // include the closing brace
903 } else {
904 // $VAR format - find end of variable name
905 end = pos + 1;
906 while (end < result.length() && (std::isalnum(result[end]) || result[end] == '_')) {
907 end++;
908 }
909 varName = result.substr(pos + 1, end - pos - 1);
910 }
911
912 if (!varName.empty()) {
913 const char *envValue = std::getenv(varName.c_str());
914 if (envValue) {
915 result.replace(start, end - start, envValue);
916 pos = start + std::strlen(envValue);
917 } else {
918 pos = end;
919 }
920 } else {
921 pos++;
922 }
923 }
924#endif
925
926 return result;
927}
928} // namespace
929
938bool ConfigServiceImpl::isExecutable(const std::string &target) const {
939 try {
940 std::filesystem::path filepath(expandEnvironmentInFilepath(target));
941
942 if (std::filesystem::exists(filepath) && std::filesystem::is_regular_file(filepath)) {
943 // On Unix-like systems, check execute permission
944 // std::filesystem doesn't have a direct equivalent to canExecute
945 // so we use std::filesystem::status
946#ifdef _WIN32
947 // On Windows, check if it's a regular file with .exe, .bat, .cmd extension
948 std::string ext = filepath.extension().string();
949 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
950 return (ext == ".exe" || ext == ".bat" || ext == ".cmd" || ext == ".com");
951#else
952 // On Unix-like systems, check execute permissions
953 auto perms = std::filesystem::status(filepath).permissions();
954 return (perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none ||
955 (perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none ||
956 (perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none;
957#endif
958 } else
959 return false;
960 } catch (const std::exception &) {
961 return false;
962 }
963}
964
977void ConfigServiceImpl::launchProcess(const std::string &programFilePath,
978 const std::vector<std::string> &programArguments) const {
979 try {
980 std::string expTarget = expandEnvironmentInFilepath(programFilePath);
981 Poco::Process::launch(expTarget, programArguments);
982 } catch (Poco::SystemException &e) {
983 throw std::runtime_error(e.what());
984 }
985}
986
992void ConfigServiceImpl::setString(const std::string &key, const std::string &value) {
993 // If the value is unchanged (after any path conversions), there's nothing to
994 // do.
995 const std::string old = getString(key);
996 if (value == old)
997 return;
998
999 // Update the internal value
1000 m_pConf->setString(key, value);
1001
1002 // Cache things that are accessed frequently
1003 if (key == "datasearch.directories") {
1005 } else if (key == "instrumentDefinition.directory") {
1007 } else if (key == "defaultsave.directory") {
1009 } else if (key == "logging.channels.consoleChannel.class") {
1010 // this key requires reloading logging for it to take effect
1012 } else if (key == LOG_LEVEL_KEY) {
1013 this->setLogLevel(value);
1014 }
1015
1016 m_notificationCenter.postNotification(new ValueChanged(key, value, old));
1017 m_changed_keys.insert(key);
1018}
1019
1027template <typename T> std::optional<T> ConfigServiceImpl::getValue(const std::string &keyName) {
1028 std::string strValue = getString(keyName);
1029 T output;
1030 int result = Mantid::Kernel::Strings::convert(strValue, output);
1031
1032 if (result != 1) {
1033 return std::nullopt;
1034 }
1035
1036 return std::optional<T>(output);
1037}
1038
1046template <> std::optional<bool> ConfigServiceImpl::getValue(const std::string &keyName) {
1047 auto returnedValue = getValue<std::string>(keyName);
1048 if (!returnedValue.has_value()) {
1049 return std::nullopt;
1050 }
1051
1052 auto &configVal = returnedValue.value();
1053
1054 std::transform(configVal.begin(), configVal.end(), configVal.begin(), ::tolower);
1055
1056 boost::trim(configVal);
1057
1058 bool trueString = configVal == "true";
1059 bool valueOne = configVal == "1";
1060 bool onOffString = configVal == "on";
1061
1062 // A string of 1 or true both count
1063 return trueString || valueOne || onOffString;
1064}
1065
1071#ifdef _WIN32
1072 return "Mantid.local.properties";
1073#else
1074 return "/etc/mantid.local.properties";
1075#endif
1076}
1077
1083
1091std::string ConfigServiceImpl::getEnvironment(const std::string &keyName) {
1092 return m_pSysConfig->getString("system.env." + keyName);
1093}
1094
1099std::string ConfigServiceImpl::getOSName() { return m_pSysConfig->getString("system.osName"); }
1100
1106 auto osArch = m_pSysConfig->getString("system.osArchitecture");
1107#ifdef __APPLE__
1108 if (osArch == "x86_64") {
1109 // This could be running under Rosetta on an Arm Mac
1110 // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment
1111 int ret = 0;
1112 size_t size = sizeof(ret);
1113 if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) != -1 && ret == 1) {
1114 osArch = "arm64_(x86_64)";
1115 g_log.warning("You are running an Intel build of Mantid on Apple silicon, which will be significantly slower and "
1116 "use more power. For best performance, install the Arm version of Mantid. This version is "
1117 "available here: https://downloads.mantidproject.org");
1118 } else {
1119 // TODO this can be removed after the v6.14 code freeze because the feature will be dropped
1120 g_log.warning("Mantid v6.14 is the last version that will support Intel macOS.");
1121 }
1122 }
1123#endif
1124 return osArch;
1125}
1126
1131std::string ConfigServiceImpl::getComputerName() { return m_pSysConfig->getString("system.nodeName"); }
1132
1137std::string ConfigServiceImpl::getOSVersion() { return m_pSysConfig->getString("system.osVersion"); }
1138
1140bool canRead(const std::string &filename) {
1141 // check for existence of the file
1142 if (!std::filesystem::exists(filename)) {
1143 return false;
1144 }
1145
1146 // Try to open the file to check if it's readable
1147 std::ifstream file(filename);
1148 return file.good();
1149}
1150
1152std::string getValueFromStdOut(const std::string &orig, const std::string &key) {
1153 size_t start = orig.find(key);
1154 if (start == std::string::npos) {
1155 return std::string();
1156 }
1157 start += key.size();
1158
1159 size_t stop = orig.find('\n', start);
1160 if (stop == std::string::npos) {
1161 return std::string();
1162 }
1163
1164 return Mantid::Kernel::Strings::strip(orig.substr(start, stop - start));
1165}
1166
1173 std::string description;
1174
1175 // read os-release
1176 static const std::string OS_RELEASE("/etc/os-release");
1177 if (canRead(OS_RELEASE)) {
1178 static const std::string PRETTY_NAME("PRETTY_NAME=");
1179
1180 // open it to see if it has the magic line
1181 std::ifstream handle(OS_RELEASE.c_str(), std::ios::in);
1182
1183 // go through the file
1184 std::string line;
1185 while (std::getline(handle, line)) {
1186 if (line.find(PRETTY_NAME) != std::string::npos) {
1187 if (line.length() > PRETTY_NAME.length() + 1) {
1188 size_t length = line.length() - PRETTY_NAME.length() - 2;
1189 description = line.substr(PRETTY_NAME.length() + 1, length);
1190 }
1191 break;
1192 }
1193 }
1194
1195 // cleanup
1196 handle.close();
1197 if (!description.empty()) {
1198 return description;
1199 }
1200 }
1201
1202 // read redhat-release
1203 static const std::string REDHAT_RELEASE("/etc/redhat-release");
1204 if (canRead(REDHAT_RELEASE)) {
1205 // open it to see if it has the magic line
1206 std::ifstream handle(REDHAT_RELEASE.c_str(), std::ios::in);
1207
1208 // go through the file
1209 std::string line;
1210 while (std::getline(handle, line)) {
1211 if (!line.empty()) {
1212 description = std::move(line);
1213 break;
1214 }
1215 }
1216
1217 // cleanup
1218 handle.close();
1219 if (!description.empty()) {
1220 return description;
1221 }
1222 }
1223
1224 // try system calls
1225 std::string cmd;
1226 std::vector<std::string> args;
1227#ifdef __APPLE__
1228 cmd = "sw_vers"; // mac
1229#elif _WIN32
1230 cmd = "wmic"; // windows
1231 args.emplace_back("os"); // windows
1232 args.emplace_back("get"); // windows
1233 args.emplace_back("Caption"); // windows
1234 args.emplace_back("/value"); // windows
1235#endif
1236
1237#if defined __APPLE__ || defined _WIN32
1238 try {
1239 Poco::Pipe outPipe, errorPipe;
1240 Poco::ProcessHandle ph = Poco::Process::launch(cmd, args, nullptr, &outPipe, &errorPipe);
1241 const int rc = ph.wait();
1242 // Only if the command returned successfully.
1243 if (rc == 0) {
1244 Poco::PipeInputStream pipeStream(outPipe);
1245 std::stringstream stringStream;
1246 Poco::StreamCopier::copyStream(pipeStream, stringStream);
1247 const std::string result = stringStream.str();
1248#ifdef __APPLE__
1249 const std::string product_name = getValueFromStdOut(result, "ProductName:");
1250 const std::string product_vers = getValueFromStdOut(result, "ProductVersion:");
1251
1252 description = product_name + " " + product_vers;
1253#elif _WIN32
1254 description = getValueFromStdOut(result, "Caption=");
1255#else
1256 UNUSED_ARG(result); // only used on mac and windows
1257#endif
1258 } else {
1259 std::stringstream messageStream;
1260 messageStream << "command \"" << cmd << "\" failed with code: " << rc;
1261 g_log.debug(messageStream.str());
1262 }
1263 } catch (Poco::SystemException &e) {
1264 g_log.debug("command \"" + cmd + "\" failed");
1265 g_log.debug(e.what());
1266 }
1267#endif
1268 return description;
1269}
1270
1273 std::string username;
1274
1275 // mac and favorite way to get username on linux
1276 try {
1277 username = m_pSysConfig->getString("system.env.USER");
1278 if (!username.empty()) {
1279 return username;
1280 }
1281 } catch (const Poco::NotFoundException &e) {
1282 UNUSED_ARG(e); // let it drop on the floor
1283 }
1284
1285 // windoze and alternate linux username variable
1286 try {
1287 username = m_pSysConfig->getString("system.env.USERNAME");
1288 if (!username.empty()) {
1289 return username;
1290 }
1291 } catch (const Poco::NotFoundException &e) {
1292 UNUSED_ARG(e); // let it drop on the floor
1293 }
1294
1295 // give up and return an empty string
1296 return std::string();
1297}
1298
1303std::string ConfigServiceImpl::getCurrentDir() { return m_pSysConfig->getString("system.currentDir"); }
1304
1310std::string ConfigServiceImpl::getCurrentDir() const { return m_pSysConfig->getString("system.currentDir"); }
1311
1316std::string ConfigServiceImpl::getTempDir() { return m_pSysConfig->getString("system.tempDir"); }
1317
1323 const std::string applicationName = "mantid";
1324#if POCO_OS == POCO_OS_WINDOWS_NT
1325 const std::string vendorName = "mantidproject";
1326 wchar_t *w_appdata = _wgetenv(L"APPDATA");
1327 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
1328 std::string appdata = converter.to_bytes(w_appdata);
1329 std::filesystem::path path(appdata);
1330 path /= vendorName;
1331 path /= applicationName;
1332 return path.string();
1333#else // linux and mac
1334 const char *home = std::getenv("HOME");
1335 if (!home) {
1336 throw std::runtime_error("HOME environment variable not set - seen in ConfigService.getAppDataDir()");
1337 }
1338 std::filesystem::path path(home);
1339 path /= ("." + applicationName);
1340 return path.string();
1341#endif
1342}
1343
1350 std::filesystem::path execPath(getPathToExecutable());
1351 return execPath.parent_path().string() + "/";
1352}
1353
1360 std::string execpath;
1361 const size_t LEN(1024);
1362 char pBuf[LEN];
1363
1364#ifdef _WIN32
1365 unsigned int bytes = GetModuleFileName(NULL, pBuf, LEN);
1366#elif defined __linux__
1367 char szTmp[32];
1368 sprintf(szTmp, "/proc/%d/exe", getpid());
1369 ssize_t bytes = readlink(szTmp, pBuf, LEN);
1370#elif defined __APPLE__
1371 // Two calls to _NSGetExecutablePath required - first to get size of buffer
1372 uint32_t bytes(0);
1373 _NSGetExecutablePath(pBuf, &bytes);
1374 const int success = _NSGetExecutablePath(pBuf, &bytes);
1375 if (success < 0)
1376 bytes = 1025;
1377#endif
1378
1379 if (bytes > 0 && bytes < 1024) {
1380 pBuf[bytes] = '\0';
1381 execpath = std::string(pBuf);
1382 }
1383 return execpath;
1384}
1385
1391bool ConfigServiceImpl::isNetworkDrive([[maybe_unused]] const std::string &path) {
1392#ifdef _WIN32
1393 // if path is relative get the full one
1394 char buff[MAX_PATH];
1395 GetFullPathName(path.c_str(), MAX_PATH, buff, NULL);
1396 std::string fullName(buff);
1397 size_t i = fullName.find(':');
1398
1399 // if the full path doesn't contain a drive letter assume it's on the network
1400 if (i == std::string::npos)
1401 return true;
1402
1403 fullName.erase(i + 1);
1404 fullName += '\\'; // make sure the name has the trailing backslash
1405 UINT type = GetDriveType(fullName.c_str());
1406 return DRIVE_REMOTE == type;
1407#elif defined __linux__
1408 // This information is only present in the /proc/mounts file on linux. There
1409 // are no drives on
1410 // linux only mount locations therefore the test will have to check the path
1411 // against
1412 // entries in /proc/mounts to see if the filesystem type is NFS or SMB (any
1413 // others ????)
1414 // Each line corresponds to a particular mounted location
1415 // 1st column - device name
1416 // 2nd column - mounted location
1417 // 3rd column - filesystem type commonly ext2, ext3 for hard drives and NFS or
1418 // SMB for
1419 // network locations
1420
1421 std::ifstream mntfile("/proc/mounts");
1422 std::string txtread("");
1423 while (getline(mntfile, txtread)) {
1424 std::istringstream strm(txtread);
1425 std::string devname(""), mntpoint(""), fstype("");
1426 strm >> devname >> mntpoint >> fstype;
1427 if (!strm)
1428 continue;
1429 // I can't be sure that the file system type is always lower case
1430 std::transform(fstype.begin(), fstype.end(), fstype.begin(), toupper);
1431 // Skip the current line if the file system isn't a network one
1432 if (fstype != "NFS" && fstype != "SMB")
1433 continue;
1434 // Now we have a line containing a network filesystem and just need to check
1435 // if the path
1436 // supplied contains the mount location. There is a small complication in
1437 // that the mount
1438 // points within the file have certain characters transformed into their
1439 // octal
1440 // representations, for example spaces->040.
1441 std::string::size_type idx = mntpoint.find("\\0");
1442 if (idx != std::string::npos) {
1443 std::string oct = mntpoint.substr(idx + 1, 3);
1444 strm.str(oct);
1445 int printch(-1);
1446 strm.setf(std::ios::oct, std::ios::basefield);
1447 strm >> printch;
1448 if (printch != -1) {
1449 mntpoint = mntpoint.substr(0, idx) + static_cast<char>(printch) + mntpoint.substr(idx + 4);
1450 }
1451 // Search for this at the start of the path
1452 if (path.find(mntpoint) == 0)
1453 return true;
1454 }
1455 }
1456 return false;
1457#else
1458 // Not yet implemented for the mac
1459 return false;
1460#endif
1461}
1462
1473
1482#ifdef _WIN32
1483 return m_strBaseDir;
1484#else
1485 std::filesystem::path datadir(m_pSysConfig->getString("system.homeDir"));
1486 datadir /= ".mantid";
1487 // Create the directory if it doesn't already exist
1488 std::filesystem::create_directory(datadir);
1489 return datadir.string() + "/";
1490#endif
1491}
1492
1497const std::vector<std::string> &ConfigServiceImpl::getDataSearchDirs() const { return m_dataSearchDirs; }
1498
1503void ConfigServiceImpl::setDataSearchDirs(const std::vector<std::string> &searchDirs) {
1504 std::string searchPaths = boost::join(searchDirs, ";");
1505 setDataSearchDirs(searchPaths);
1506}
1507
1513void ConfigServiceImpl::setDataSearchDirs(const std::string &searchDirs) {
1514 setString("datasearch.directories", searchDirs);
1515}
1516
1522void ConfigServiceImpl::appendDataSearchSubDir(const std::string &subdir) {
1523 if (subdir.empty())
1524 return;
1525
1526 std::filesystem::path subDirPath;
1527 try {
1528 subDirPath = std::filesystem::path(subdir);
1529 } catch (const std::exception &) {
1530 return;
1531 }
1532
1533 // Check if path has trailing slash (indicates directory) and is relative
1534 if (!subDirPath.is_relative()) {
1535 return;
1536 }
1537
1538 auto newDataDirs = m_dataSearchDirs;
1539 for (const auto &path : m_dataSearchDirs) {
1540 std::filesystem::path newDirPath;
1541 try {
1542 newDirPath = std::filesystem::path(path) / subDirPath;
1543 // only add new path if it isn't already there
1544 if (std::find(newDataDirs.begin(), newDataDirs.end(), newDirPath.string()) == newDataDirs.end())
1545 newDataDirs.emplace_back(newDirPath.string());
1546 } catch (const std::exception &) {
1547 continue;
1548 }
1549 }
1550
1551 setDataSearchDirs(newDataDirs);
1552}
1553
1559void ConfigServiceImpl::appendDataSearchDir(const std::string &path) {
1560 if (path.empty())
1561 return;
1562
1563 std::filesystem::path dirPath;
1564 try {
1565 dirPath = std::filesystem::path(path);
1566 // Ensure it has a trailing slash for consistency
1567 std::string pathStr = dirPath.string();
1568 if (pathStr.back() != '/' && pathStr.back() != '\\') {
1569 pathStr += "/";
1570 }
1571 if (!isInDataSearchList(pathStr)) {
1572 auto newSearchString = Strings::join(std::begin(m_dataSearchDirs), std::end(m_dataSearchDirs), ";");
1573 newSearchString.append(";" + path);
1574 setString("datasearch.directories", newSearchString);
1575 }
1576 } catch (const std::exception &) {
1577 return;
1578 }
1579}
1580
1585void ConfigServiceImpl::setInstrumentDirectories(const std::vector<std::string> &directories) {
1586 m_instrumentDirs = directories;
1587}
1588
1593const std::vector<std::string> &ConfigServiceImpl::getInstrumentDirectories() const { return m_instrumentDirs; }
1594
1599const std::string ConfigServiceImpl::getInstrumentDirectory() const { return m_instrumentDirs.back(); }
1605 // Determine the search directory for XML instrument definition files (IDFs)
1606 std::string directoryName = getString("instrumentDefinition.vtp.directory");
1607
1608 if (directoryName.empty()) {
1609 std::filesystem::path path(getAppDataDir());
1610 path /= "instrument";
1611 path /= "geometryCache";
1612 directoryName = path.string();
1613 }
1614 return directoryName;
1615}
1627 m_instrumentDirs.clear();
1628
1629 std::filesystem::path path(getAppDataDir());
1630 path /= "instrument";
1631 const std::string appdatadir = path.string();
1633
1634#ifndef _WIN32
1635 addDirectoryifExists("/etc/mantid/instrument", m_instrumentDirs);
1636#endif
1637
1638 // Determine the search directory for XML instrument definition files (IDFs)
1639 std::string directoryName = getString("instrumentDefinition.directory", true);
1640 if (directoryName.empty()) {
1641 // This is the assumed deployment directory for IDFs, where we need to be
1642 // relative to the
1643 // directory of the executable, not the current working directory.
1644 std::filesystem::path basePath(getPropertiesDir());
1645 directoryName = (basePath / ".." / "instrument").lexically_normal().string();
1646 }
1647 addDirectoryifExists(directoryName, m_instrumentDirs);
1648}
1649
1657bool ConfigServiceImpl::addDirectoryifExists(const std::string &directoryName,
1658 std::vector<std::string> &directoryList) {
1659 try {
1660 if (std::filesystem::is_directory(directoryName)) {
1661 directoryList.emplace_back(directoryName);
1662 return true;
1663 } else {
1664 g_log.information("Unable to locate directory at: " + directoryName);
1665 return false;
1666 }
1667 } catch (const std::filesystem::filesystem_error &) {
1668 g_log.information("Unable to locate directory at: " + directoryName);
1669 return false;
1670 } catch (const std::exception &) {
1671 g_log.information("Unable to locate directory at: " + directoryName);
1672 return false;
1673 }
1674}
1675
1676const std::vector<std::string> ConfigServiceImpl::getFacilityFilenames(const std::string &fName) {
1677 std::vector<std::string> returnPaths;
1678
1679 // first try the supplied file
1680 if (!fName.empty()) {
1681 if (std::filesystem::exists(fName)) {
1682 returnPaths.emplace_back(fName);
1683 return returnPaths;
1684 }
1685 }
1686
1687 // search all of the instrument directories
1688 const auto &directoryNames = getInstrumentDirectories();
1689
1690 // only use downloaded instruments if configured to download
1691 const std::string updateInstrStr = this->getString("UpdateInstrumentDefinitions.OnStartup");
1692
1693 auto instrDir = directoryNames.begin();
1694
1695 // If we are not updating the instrument definitions
1696 // update the iterator, this means we will skip the folder in HOME and
1697 // look in the instrument folder in mantid install directory or mantid source
1698 // code directory
1699 if (!(updateInstrStr == "1" || updateInstrStr == "on" || updateInstrStr == "On") && directoryNames.size() > 1) {
1700 instrDir++;
1701 }
1702
1703 // look through all the possible files
1704 for (; instrDir != directoryNames.end(); ++instrDir) {
1705 std::filesystem::path p(*instrDir);
1706 p /= "Facilities.xml";
1707 std::string filename = p.string();
1708
1709 if (std::filesystem::exists(filename))
1710 returnPaths.emplace_back(filename);
1711 }
1712
1713 if (returnPaths.size() > 0) {
1714 return returnPaths;
1715 }
1716
1717 // getting this far means the file was not found
1718 std::string directoryNamesList = boost::algorithm::join(directoryNames, ", ");
1719 throw std::runtime_error("Failed to find \"Facilities.xml\". Searched in " + directoryNamesList);
1720}
1721
1732void ConfigServiceImpl::updateFacilities(const std::string &fName) {
1734
1735 // Try to find the file. If it does not exist we will crash, and cannot read
1736 // the Facilities file
1737 const auto fileNames = getFacilityFilenames(fName);
1738 size_t attemptIndex = 0;
1739 bool success = false;
1740 while ((!success) && (attemptIndex < fileNames.size())) {
1741 const auto &fileName = fileNames[attemptIndex];
1742 try {
1743 // Set up the DOM parser and parse xml file
1744 Poco::AutoPtr<Poco::XML::Document> pDoc;
1745 try {
1746 Poco::XML::DOMParser pParser;
1747 pDoc = pParser.parse(fileName);
1748 } catch (...) {
1749 throw Kernel::Exception::FileError("Unable to parse file:", fileName);
1750 }
1751
1752 // Get pointer to root element
1753 Poco::XML::Element *pRootElem = pDoc->documentElement();
1754 if (!pRootElem->hasChildNodes()) {
1755 throw std::runtime_error("No root element in Facilities.xml file");
1756 }
1757
1758 const Poco::AutoPtr<Poco::XML::NodeList> pNL_facility = pRootElem->getElementsByTagName("facility");
1759 const size_t n = pNL_facility->length();
1760
1761 for (unsigned long i = 0; i < n; ++i) {
1762 const auto *elem = dynamic_cast<Poco::XML::Element *>(pNL_facility->item(i));
1763 if (elem) {
1764 m_facilities.emplace_back(new FacilityInfo(elem));
1765 }
1766 }
1767
1768 if (m_facilities.empty()) {
1769 throw std::runtime_error("The facility definition file " + fileName + " defines no facilities");
1770 }
1771
1772 // if we got here we have suceeded and can exit the loop
1773 success = true;
1774 } catch (std::runtime_error &ex) {
1775 // log this failure to load a file
1776 g_log.error() << "Failed to load the facilities.xml file at " << fileName << "\nIt might be corrupt. "
1777 << ex.what() << "\nWill try to load another version.\n";
1778 attemptIndex++;
1779 // move on to the next file index if available
1780 if (attemptIndex == fileNames.size()) {
1781 const std::string errorMessage = "No more Facilities.xml files can be found, Mantid will not be "
1782 "able to start, Sorry. Try reinstalling Mantid.";
1783 // This is one of the few times that both logging a messge and throwing
1784 // might make sense
1785 // as the error reporter tends to swallow the thrown message.
1786 g_log.error() << errorMessage << "\n";
1787 // Throw an exception as we have run out of files to try
1788 throw std::runtime_error(errorMessage);
1789 }
1790 }
1791 }
1792}
1793
1797 for (const auto &facility : m_facilities) {
1798 delete facility;
1799 }
1800 m_facilities.clear();
1801}
1802
1809const InstrumentInfo &ConfigServiceImpl::getInstrument(const std::string &instrumentName) const {
1810
1811 // Let's first search for the instrument in our default facility
1812 std::string defaultFacility = getFacility().name();
1813
1814 if (!defaultFacility.empty()) {
1815 try {
1816 g_log.debug() << "Looking for " << instrumentName << " at " << defaultFacility << ".\n";
1817 return getFacility(defaultFacility).instrument(instrumentName);
1818 } catch (Exception::NotFoundError &) {
1819 // Well the instName doesn't exist for this facility
1820 // Move along, there's nothing to see here...
1821 }
1822 }
1823
1824 // Now let's look through the other facilities
1825 for (auto facility : m_facilities) {
1826 try {
1827 g_log.debug() << "Looking for " << instrumentName << " at " << (*facility).name() << ".\n";
1828 return (*facility).instrument(instrumentName);
1829 } catch (Exception::NotFoundError &) {
1830 // Well the instName doesn't exist for this facility...
1831 // Move along, there's nothing to see here...
1832 }
1833 }
1834
1835 const std::string errMsg = "Failed to find an instrument with this name in any facility: '" + instrumentName + "' -";
1836 g_log.debug("Instrument " + instrumentName + " not found");
1837 throw Exception::NotFoundError(errMsg, instrumentName);
1838}
1839
1843const std::vector<FacilityInfo *> ConfigServiceImpl::getFacilities() const { return m_facilities; }
1844
1848const std::vector<std::string> ConfigServiceImpl::getFacilityNames() const {
1849 auto names = std::vector<std::string>(m_facilities.size());
1850 std::transform(m_facilities.cbegin(), m_facilities.cend(), names.begin(),
1851 [](const FacilityInfo *facility) { return facility->name(); });
1852
1853 return names;
1854}
1855
1860 std::string defFacility = getString("default.facility");
1861 if (defFacility.empty()) {
1862 defFacility = " ";
1863 }
1864 return this->getFacility(defFacility);
1865}
1866
1873const FacilityInfo &ConfigServiceImpl::getFacility(const std::string &facilityName) const {
1874 if (facilityName.empty())
1875 return this->getFacility();
1876
1877 auto facility = std::find_if(m_facilities.cbegin(), m_facilities.cend(),
1878 [&facilityName](const auto f) { return f->name() == facilityName; });
1879
1880 if (facility != m_facilities.cend()) {
1881 return **facility;
1882 }
1883
1884 throw Exception::NotFoundError("Facilities", facilityName);
1885}
1886
1892void ConfigServiceImpl::setFacility(const std::string &facilityName) {
1893 const FacilityInfo *foundFacility = nullptr;
1894
1895 try {
1896 // Get facility looks up by string - so re-use that to check if the facility
1897 // is known
1898 foundFacility = &getFacility(facilityName);
1899 } catch (const Exception::NotFoundError &) {
1900 g_log.error("Failed to set default facility to be " + facilityName + ". Facility not found");
1901 throw;
1902 }
1903 assert(foundFacility);
1904 setString("default.facility", facilityName);
1905
1906 const auto &associatedInsts = foundFacility->instruments();
1907 if (associatedInsts.empty()) {
1908 throw std::invalid_argument("The selected facility has no instruments associated with it");
1909 }
1910
1911 // Update the default instrument to be one from this facility
1912 setString("default.instrument", associatedInsts[0].name());
1913}
1914
1918void ConfigServiceImpl::addObserver(const Poco::AbstractObserver &observer) const {
1919 m_notificationCenter.addObserver(observer);
1920}
1921
1925void ConfigServiceImpl::removeObserver(const Poco::AbstractObserver &observer) const {
1926 m_notificationCenter.removeObserver(observer);
1927}
1928
1929/*
1930Gets the system proxy information
1931@url A url to match the proxy to
1932@return the proxy information.
1933*/
1935 if (!m_isProxySet) {
1936 // set the proxy
1937 // first check if the proxy is defined in the properties file
1938 auto proxyHost = getValue<std::string>("proxy.host");
1939 auto proxyPort = getValue<int>("proxy.port");
1940
1941 if (proxyHost.has_value() && proxyPort.has_value()) {
1942 // set it from the config values
1943 m_proxyInfo = ProxyInfo(proxyHost.value(), proxyPort.value(), true);
1944 } else {
1945 // get the system proxy
1946 Poco::URI uri(url);
1947 Mantid::Kernel::NetworkProxy proxyHelper;
1948 m_proxyInfo = proxyHelper.getHttpProxy(uri.toString());
1949 }
1950 m_isProxySet = true;
1951 }
1952 return m_proxyInfo;
1953}
1954
1955std::string ConfigServiceImpl::getFullPath(const std::string &filename, const bool ignoreDirs,
1956 const int options) const {
1957 std::string fName = Kernel::Strings::strip(filename);
1958 g_log.debug() << "getFullPath(" << fName << ")\n";
1959 // If this is already a full path, nothing to do
1960 std::filesystem::path filepath(fName);
1961 if (filepath.is_absolute())
1962 return fName;
1963 // First try the path relative to the current directory. Can throw in some
1964 // circumstances with extensions that have wild cards
1965 try {
1966 std::filesystem::path fullPath = std::filesystem::current_path() / fName;
1967 if (std::filesystem::exists(fullPath) && (!ignoreDirs || !std::filesystem::is_directory(fullPath)))
1968 return fullPath.string();
1969 } catch (const std::exception &) {
1970 }
1971
1972 auto directoryNames = getDataSearchDirs();
1973 const auto &instrDirectories = getInstrumentDirectories();
1974 directoryNames.insert(directoryNames.end(), instrDirectories.begin(), instrDirectories.end());
1975 for (const auto &searchPath : directoryNames) {
1976 g_log.debug() << "Searching for " << fName << " in " << searchPath << "\n";
1977// On windows globbing is not working properly with network drives
1978// for example a network drive containing a $
1979// For this reason, and since windows is case insensitive anyway
1980// a special case is made for windows
1981#ifdef _WIN32
1982 if (fName.find("*") != std::string::npos) {
1983#endif
1984 std::filesystem::path path = std::filesystem::path(searchPath) / fName;
1985 std::set<std::string> files;
1986 Kernel::Glob::glob(path.string(), files, options);
1987 if (!files.empty()) {
1988 std::filesystem::path matchPath(*files.begin());
1989 if (ignoreDirs && std::filesystem::is_directory(matchPath)) {
1990 continue;
1991 }
1992 return *files.begin();
1993 }
1994#ifdef _WIN32
1995 } else {
1996 std::filesystem::path path = std::filesystem::path(searchPath) / fName;
1997 if (std::filesystem::exists(path) && !(ignoreDirs && std::filesystem::is_directory(path))) {
1998 return path.string();
1999 }
2000 }
2001#endif
2002 }
2003 return "";
2004}
2005
2011void ConfigServiceImpl::setLogLevel(int logLevel, bool quiet) {
2013 // update the internal value to keep strings in sync
2014 m_pConf->setString(LOG_LEVEL_KEY, g_log.getLevelName());
2015
2016 if (!quiet) {
2017 g_log.log("logging set to " + Logger::PriorityNames[std::size_t(logLevel)] + " priority",
2018 static_cast<Logger::Priority>(logLevel));
2019 }
2020}
2021
2022void ConfigServiceImpl::setLogLevel(std::string const &logLevel, bool quiet) {
2024 // update the internal value to keep strings in sync
2025 m_pConf->setString(LOG_LEVEL_KEY, g_log.getLevelName());
2026
2027 if (!quiet) {
2028 g_log.log("logging set to " + logLevel + " priority", static_cast<Logger::Priority>(g_log.getLevel()));
2029 }
2030}
2031
2033
2035template DLLExport std::optional<double> ConfigServiceImpl::getValue(const std::string &);
2036template DLLExport std::optional<std::string> ConfigServiceImpl::getValue(const std::string &);
2037template DLLExport std::optional<int> ConfigServiceImpl::getValue(const std::string &);
2038template DLLExport std::optional<size_t> ConfigServiceImpl::getValue(const std::string &);
2039#ifdef _MSC_VER
2040template DLLExport std::optional<bool> ConfigServiceImpl::getValue(const std::string &);
2041#endif
2042
2044
2045} // namespace Kernel
2046} // namespace Mantid
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
#define DLLExport
Definitions of the DLLImport compiler directives for MSVC.
Definition System.h:37
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:48
This is the class for the notification that is to be sent when a value has been changed in config ser...
const std::vector< std::string > & getInstrumentDirectories() const
Get instrument search directories.
std::string m_propertyString
The configuration properties in string format.
void setBaseDirectory()
Setup the base directory.
const std::string getVTPFileDirectory()
get the vtp file directory
const std::string m_properties_file_name
The filename of the Mantid properties file.
std::set< std::string > m_configPaths
List of config paths that may be relative.
void setInstrumentDirectories(const std::vector< std::string > &directories)
Sets instrument directories.
ConfigServiceImpl()
Private constructor for singleton class.
const FacilityInfo & getFacility() const
Get the default facility.
std::string getDirectoryOfExecutable() const
Get the directory containing the program executable.
bool addDirectoryifExists(const std::string &directoryName, std::vector< std::string > &directoryList)
Verifies the directory exists and add it to the back of the directory list if valid.
void cacheInstrumentPaths()
Create the storage of the instrument directories.
void reset()
Reset to "factory" settings. Removes current user properties.
std::string getLocalFilename() const
Return the local properties filename.
std::string getAppDataDir()
Returns the system's appdata directory.
std::string getPropertiesDir() const
Returns the directory where the Mantid.properties file is found.
std::string getOSVersion()
Returns the OS version.
std::string getOSVersionReadable()
Returns a human readable version of the OS version.
virtual ~ConfigServiceImpl()
Private Destructor Prevents client from calling 'delete' on the pointer handed out by Instance.
std::string getFullPath(const std::string &filename, const bool ignoreDirs, const int options) const
std::string getPathToExecutable() const
Get the full path to the executing program (i.e.
void loadConfig(const std::string &filename, const bool append=false)
Loads a config file.
void configureLogging()
Configures the Poco logging and starts it up.
std::string getUserPropertiesDir() const
Returns a directory to use to write out Mantid information.
bool isExecutable(const std::string &target) const
Checks to see whether the target passed is an executable file.
void saveConfig(const std::string &filename) const
Save the configuration to the user file.
const std::vector< std::string > & getDataSearchDirs() const
Get the list of search paths.
bool isInDataSearchList(const std::string &path) const
Returns true if the path is in the data search list.
const std::vector< std::string > getFacilityFilenames(const std::string &fName)
Determine the name of the facilities file to use.
void removeObserver(const Poco::AbstractObserver &observer) const
Remove an observer.
std::string m_strBaseDir
The directory that is considered to be the base directory.
Kernel::ProxyInfo & getProxy(const std::string &url)
Gets the proxy for the system.
std::string makeAbsolute(const std::string &dir, const std::string &key) const
Make a relative path or a list of relative paths into an absolute one.
const InstrumentInfo & getInstrument(const std::string &instrumentName="") const
Look for an instrument.
std::set< std::string > m_changed_keys
A set of property keys that have been changed.
void updateConfig(const std::string &filename, const bool append=false, const bool update_caches=true)
Wipe out the current configuration and load a new one.
void createUserPropertiesFile() const
Writes out a fresh user properties file.
std::vector< std::string > m_dataSearchDirs
Store a list of data search paths.
void updateFacilities(const std::string &fName="")
Load facility information from instrumentDir/Facilities.xml file.
const std::vector< FacilityInfo * > getFacilities() const
Get the list of facilities.
std::string getEnvironment(const std::string &keyName)
Searches for the given environment variable and returns it as a string.
void appendDataSearchDir(const std::string &path)
Adds the passed path to the end of the list of data search paths.
std::string getCurrentDir()
Returns the current directory.
void getKeysRecursive(const std::string &root, std::vector< std::string > &allKeys) const
Returns a list of all keys under a given root key.
void setLogLevel(int logLevel, bool quiet=false)
Sets the log level priority for all log channels.
std::optional< T > getValue(const std::string &keyName)
Searches for a string within the currently loaded configuration values and attempts to convert the va...
void setDataSearchDirs(const std::vector< std::string > &searchDirs)
Set a list of search paths via a vector.
std::string getString(const std::string &keyName, bool pathAbsolute=true) const
Searches for a configuration property.
const std::string getInstrumentDirectory() const
Get instrument search directory.
void addObserver(const Poco::AbstractObserver &observer) const
Add an observer for a notification.
std::string getUsername()
Returns the username.
std::string getOSName()
Returns the OS name.
void setString(const std::string &key, const std::string &value)
Sets a configuration property.
void clearFacilities()
Empty the list of facilities, deleting the FacilityInfo objects in the process.
std::string getComputerName()
Returns the computer name.
std::string getOSArchitecture()
Returns the architecture.
std::string getUserFilename() const
Return the user properties filename.
Poco::AutoPtr< Poco::Util::SystemConfiguration > m_pSysConfig
the POCO system Config Object
bool m_isProxySet
whether the proxy has been populated yet
bool isNetworkDrive(const std::string &path)
Check if the path is on a network drive.
bool readFile(const std::string &filename, std::string &contents) const
Read a file and place its contents into the given string.
Poco::NotificationCenter m_notificationCenter
Handles distribution of Poco signals.
Kernel::ProxyInfo m_proxyInfo
local cache of proxy details
void setFacility(const std::string &facilityName)
Set the default facility.
void remove(const std::string &rootName)
Removes the value from a selected keyName.
void cacheDataSearchPaths()
Create the storage of the data search directories.
std::string getTempDir()
Returns the system's temp directory.
const std::string m_user_properties_file_name
The filename of the Mantid user properties file.
void appendDataSearchSubDir(const std::string &subdir)
Appends subdirectory to each of the specified data search directories.
std::vector< std::string > keys() const
Returns a list of all full keys in the config.
Poco::AutoPtr< Poco::Util::PropertyFileConfiguration > m_pConf
the POCO file config object
const std::vector< std::string > getFacilityNames() const
Get the list of facility names.
std::vector< FacilityInfo * > m_facilities
The list of available facilities.
void launchProcess(const std::string &programFilePath, const std::vector< std::string > &programArguments) const
Launches a process i.e opening a program.
std::vector< std::string > getKeys(const std::string &keyName) const
Searches for a key in the configuration property.
std::vector< std::string > m_instrumentDirs
Store a list of instrument directory paths.
bool hasProperty(const std::string &rootName) const
Checks to see whether a key has a value assigned to it.
Records the filename and the description of failure.
Definition Exception.h:98
Exception for when an item is not found in a collection.
Definition Exception.h:145
A class that holds information about a facility.
const std::vector< InstrumentInfo > & instruments() const
Returns a list of instruments of this facility.
const std::string & name() const
Return the name of the facility.
const InstrumentInfo & instrument(std::string iName="") const
Returns instruments with given name.
static void glob(const std::string &pathPattern, std::set< std::string > &files, int options=0)
Creates a set of files that match the given pathPattern.
Definition Glob.cpp:35
A class that holds information about an instrument.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
static void setLevelForAll(const int level)
Sets the log level for all Loggers created so far, including the root logger.
Definition Logger.cpp:337
static void shutdown()
Shuts down the logging framework and releases all Loggers.
Definition Logger.cpp:322
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
std::string getLevelName() const
Definition Logger.cpp:219
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
Poco::Message::Priority Priority
Definition Logger.h:54
void log(const std::string &message, const Priority &priority)
Log a message at a given priority.
Definition Logger.cpp:359
int getLevel() const
Returns the Logger's log level.
Definition Logger.cpp:217
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
static const std::array< std::string, 9 > PriorityNames
Definition Logger.h:56
static const std::string & revision()
The abbreviated SHA-1 of the last commit.
static const std::string & version()
The full version number.
static std::string doi()
The DOI for this release of Mantid.
static const std::string & paperCitation()
The citation for the Mantid paper.
NetworkProxy : Network proxy utility for getting network proxy information.
ProxyInfo getHttpProxy(const std::string &targetURLString)
Get http proxy information.
ProxyInfo : Container for carrying around network proxy information.
Definition ProxyInfo.h:17
Iterator begin()
Iterator referring to first element in the container.
@ TOK_IGNORE_EMPTY
ignore empty tokens
@ TOK_TRIM
remove leading and trailing whitespace from tokens
Iterator end()
Iterator referring to the past-the-end element in the container.
MANTID_KERNEL_DLL std::string strip(const std::string &A)
strip pre/post spaces
Definition Strings.cpp:419
DLLExport std::string join(ITERATOR_TYPE begin, ITERATOR_TYPE end, const std::string &separator, typename std::enable_if<!(std::is_same< typename std::iterator_traits< ITERATOR_TYPE >::iterator_category, std::random_access_iterator_tag >::value)>::type *=nullptr)
Join a set or vector of (something that turns into a string) together into one string,...
Definition Strings.h:84
int convert(const std::string &A, T &out)
Convert a string into a number.
Definition Strings.cpp:696
std::string getValueFromStdOut(const std::string &orig, const std::string &key)
bool canRead(const std::string &filename)
Mantid::Kernel::StringTokenizer tokenizer
Definition Material.cpp:18
Helper class which provides the Collimation Length for SANS instruments.
MANTID_KERNEL_DLL std::string welcomeMessage()
Returns the welcome message for Mantid.