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 <exception>
58#include <filesystem>
59#include <fstream>
60#include <functional>
61#include <iostream>
62#include <locale>
63#include <stdexcept>
64#include <utility>
65
66#ifdef __APPLE__
67#include <mach-o/dyld.h>
68#include <sys/sysctl.h>
69#endif
70
71namespace Mantid {
76std::string welcomeMessage() {
77 return "Welcome to Mantid " + std::string(Mantid::Kernel::MantidVersion::version()) +
79 " and this release: " + Mantid::Kernel::MantidVersion::doi();
80}
81
82namespace Kernel {
83
84namespace { // anonymous namespace for some utility functions
85
87Logger g_log("ConfigService");
88
89const std::string PATH_DELIMITERS = ";,";
90
97std::vector<std::string> splitPath(const std::string &path) {
98 std::vector<std::string> splitted;
99
100 if (path.find_first_of(PATH_DELIMITERS) == std::string::npos) { // don't bother tokenizing
101 splitted.emplace_back(path);
102 } else {
104 Mantid::Kernel::StringTokenizer tokenizer(path, PATH_DELIMITERS, options);
105 auto iend = tokenizer.end();
106 for (auto itr = tokenizer.begin(); itr != iend; ++itr) {
107 if (!itr->empty()) {
108 splitted.emplace_back(*itr);
109 }
110 }
111 }
112 return splitted;
113}
114
115const std::string LOG_LEVEL_KEY("logging.loggers.root.level");
116
117} // end of anonymous namespace
118
119//-------------------------------
120// Private member functions
121//-------------------------------
122
125 : m_pConf(nullptr), m_pSysConfig(new Poco::Util::SystemConfiguration()), m_changed_keys(), m_strBaseDir(""),
126 m_propertyString(""), m_properties_file_name("Mantid.properties"),
127 m_user_properties_file_name("Mantid.user.properties"), m_dataSearchDirs(), m_instrumentDirs(), m_proxyInfo(),
128 m_isProxySet(false) {
129 // Register StdChannel with Poco
130 Poco::LoggingFactory::defaultFactory().registerChannelClass(
131 "StdoutChannel", new Poco::Instantiator<Poco::StdoutChannel, Poco::Channel>);
132
134
135 m_configPaths.insert("framework.plugins.directory");
136 m_configPaths.insert("mantidqt.plugins.directory");
137 m_configPaths.insert("instrumentDefinition.directory");
138 m_configPaths.insert("instrumentDefinition.vtpDirectory");
139 m_configPaths.insert("groupingFiles.directory");
140 m_configPaths.insert("maskFiles.directory");
141 m_configPaths.insert("colormaps.directory");
142 m_configPaths.insert("requiredpythonscript.directories");
143 m_configPaths.insert("pythonscripts.directory");
144 m_configPaths.insert("pythonscripts.directories");
145 m_configPaths.insert("python.plugins.directories");
146 m_configPaths.insert("user.python.plugins.directories");
147 m_configPaths.insert("icatDownload.directory");
148 m_configPaths.insert("datasearch.directories");
149 m_configPaths.insert("python.plugins.manifest");
150 m_configPaths.insert("python.templates.directory");
151
152 // attempt to load the default properties file that resides in the directory
153 // of the executable
154 std::string propertiesFilesList;
156 propertiesFilesList = getPropertiesDir() + m_properties_file_name;
157
158 // Load the local (machine) properties file, if it exists
159 if (std::filesystem::exists(getLocalFilename())) {
160 updateConfig(getLocalFilename(), true, false);
161 propertiesFilesList += ", " + getLocalFilename();
162 }
163
164 if (Poco::Environment::has("MANTIDPROPERTIES")) {
165 // and then append the user properties
166 updateConfig(getUserFilename(), true, false);
167 propertiesFilesList += ", " + getUserFilename();
168 // and the extra one from the environment
169 updateConfig(Poco::Environment::get("MANTIDPROPERTIES"), true, true);
170 propertiesFilesList += ", " + Poco::Environment::get("MANTIDPROPERTIES");
171 } else {
172 // Just do the user properties
173 updateConfig(getUserFilename(), true, true);
174 propertiesFilesList += ", " + getUserFilename();
175 }
176
177 g_log.debug() << "ConfigService created.\n";
178 g_log.debug() << "Configured Mantid.properties directory of application as " << getPropertiesDir() << '\n';
179 g_log.information() << "This is Mantid version " << MantidVersion::version() << " revision "
180 << MantidVersion::revision() << '\n';
181 g_log.information() << "running on " << getComputerName() << " starting "
182 << Types::Core::DateAndTime::getCurrentTime().toFormattedString("%Y-%m-%dT%H:%MZ") << "\n";
183 g_log.information() << "Properties file(s) loaded: " << propertiesFilesList << '\n';
184
185 // Assert that the appdata and the instrument subdirectory exists
186 std::string appDataDir = getAppDataDir();
187 std::filesystem::path path = std::filesystem::path(appDataDir) / "instrument";
188 // create_directories will fail gracefully if it is already present - but will
189 // throw an error if it cannot create the directory
190 try {
191 std::filesystem::create_directories(path);
192 } catch (const std::filesystem::filesystem_error &fe) {
193 g_log.error() << "Cannot create the local instrument cache directory [" << path.string()
194 << "]. Mantid will not be able to update instrument definitions.\n"
195 << fe.what() << '\n';
196 }
197 std::filesystem::path vtpDir(getVTPFileDirectory());
198 try {
199 std::filesystem::create_directories(vtpDir);
200 } catch (const std::filesystem::filesystem_error &fe) {
201 g_log.error() << "Cannot create the local instrument geometry cache directory [" << vtpDir.string()
202 << "]. Mantid will be slower at viewing complex instruments.\n"
203 << fe.what() << '\n';
204 }
205 // must update the cache of instrument paths
207
208 // update the facilities AFTER we have ensured that all of the directories are
209 // created and the paths updated
210 // if we don't do that first the function below will silently fail without
211 // initialising the facilities vector
212 // and Mantid will crash when it tries to access them, for example when
213 // creating the first time startup screen
215}
216
224
238 // Define the directory to search for the Mantid.properties file.
239 std::filesystem::path filepath;
240
241 // First directory: the current working
242 m_strBaseDir = std::filesystem::current_path().string() + "/";
243 filepath = std::filesystem::path(m_strBaseDir) / m_properties_file_name;
244 if (std::filesystem::exists(filepath))
245 return;
246
247 // Check the executable directory to see if it includes a mantid.properties
248 // file
250 filepath = std::filesystem::path(m_strBaseDir) / m_properties_file_name;
251 if (std::filesystem::exists(filepath))
252 return;
253
254 // Check the MANTIDPATH environment var
255 if (Poco::Environment::has("MANTIDPATH")) {
256 // Here we have to follow the convention of the rest of this code and
257 // add a trailing slash.
258 // Note: adding it to the MANTIDPATH itself will make other parts of the
259 // code crash.
260#ifdef _WIN32
261 // In case the path contains backslashes, we cannot
262 // mix forward and back slashes in the path.
263 m_strBaseDir = Poco::Environment::get("MANTIDPATH") + "\\";
264#else
265 m_strBaseDir = Poco::Environment::get("MANTIDPATH") + "/";
266#endif
267 filepath = std::filesystem::path(m_strBaseDir) / m_properties_file_name;
268 if (std::filesystem::exists(filepath))
269 return;
270 }
271
272#ifdef __APPLE__
273 // Finally, on OSX check if we're in the package directory and the .properties
274 // file just happens to be two directories up
275 std::filesystem::path execPath(getDirectoryOfExecutable());
276 m_strBaseDir = execPath.parent_path().parent_path().parent_path().string() + "/";
277#endif
278}
279
280namespace {
281// look for specific keys and throw an exception if one is found
282std::string checkForBadConfigOptions(const std::string &filename, const std::string &propertiesString) {
283 std::stringstream stream(propertiesString);
284 std::stringstream resultPropertiesString;
285 std::string line;
286 int line_num = 0;
287 while (std::getline(stream, line)) {
288 line_num += 1; // increment early
289 bool is_ok = true;
290
291 // Check for common errors. Empty lines are ok, things that are a key
292 // without a value are a critical failure. Forbidden keys are just commented
293 // out.
294 if (line.empty() || (Kernel::Strings::strip(line)[0] == '#')) {
295 // do nothing
296 } else if (line.find("FilterChannel") != std::string::npos) {
297 is_ok = false;
298 }
299
300 // Print warning to error channel and comment out offending line
301 if (!is_ok) {
302 const auto end = line.find("=");
303 g_log.warning() << "Encontered invalid key \"";
304 if (end != std::string::npos) {
305 g_log.warning() << Kernel::Strings::strip(line.substr(0, end));
306 } else {
308 }
309 g_log.warning() << "\" in " << filename << " on line " << line_num << std::endl;
310
311 // comment out the property
312 resultPropertiesString << '#';
313 }
314 // copy over the line
315 resultPropertiesString << line << '\n';
316 }
317 return resultPropertiesString.str();
318}
319} // end of anonymous namespace
320
330void ConfigServiceImpl::loadConfig(const std::string &filename, const bool append) {
331
332 if (!append) {
333 // remove the previous property string
334 m_propertyString = "";
335 m_changed_keys.clear();
336 }
337
338 try {
339 // slurp in entire file
340 std::string temp;
341 bool good = readFile(filename, temp);
342
343 // check if we have failed to open the file
344 if ((!good) || (temp.empty())) {
346 // write out a fresh file
348 } else {
349 throw Exception::FileError("Cannot open file", filename);
350 }
351 }
352
353 // verify the contents and comment out offending lines
354 temp = checkForBadConfigOptions(filename, temp);
355
356 // store the property string
357 if ((append) && (!m_propertyString.empty())) {
358 m_propertyString = m_propertyString + "\n" + temp;
359 } else {
360 m_propertyString = temp;
361 }
362 } catch (std::exception &e) {
363 // there was a problem loading the file - it probably is not there
364 g_log.error() << "Problem loading the configuration file " << filename << " " << e.what() << '\n';
365 g_log.error() << "Mantid is unable to start.\n" << std::endl;
366 throw;
367 }
368
369 // use the cached property string to initialise the POCO property file
370 std::istringstream istr(m_propertyString);
371 m_pConf = new Poco::Util::PropertyFileConfiguration(istr);
372}
373
380bool ConfigServiceImpl::readFile(const std::string &filename, std::string &contents) const {
381 std::ifstream propFile(filename.c_str(), std::ios::in);
382 bool good = propFile.good();
383 if (!good) {
384 contents = "";
385 propFile.close();
386 return good;
387 }
388
389 // slurp in entire file - extremely unlikely delimiter used as an alternate to
390 // \n
391 contents.clear();
392 getline(propFile, contents, '`');
393 propFile.close();
394 return good;
395}
396
401 try {
402 // Configure the logging framework
403 Poco::Util::LoggingConfigurator configurator;
404#if POCO_VERSION > 0x01090400
405 configurator.configure(m_pConf);
406#else
407 configurator.configure(m_pConf.get());
408#endif
409 } catch (std::exception &e) {
410 std::cerr << "Trouble configuring the logging framework " << e.what() << '\n';
411 }
412}
413
421std::string ConfigServiceImpl::makeAbsolute(const std::string &dir, const std::string &key) const {
422 if (dir.empty()) {
423 // Don't do anything for an empty value
424 return dir;
425 }
426 std::string converted;
427 // If we have a list, chop it up and convert each one
428 if (dir.find_first_of(PATH_DELIMITERS) != std::string::npos) {
429 auto splitted = splitPath(dir);
430 auto iend = splitted.cend();
431 for (auto itr = splitted.begin(); itr != iend;) {
432 std::string absolute = makeAbsolute(*itr, key);
433 if (absolute.empty()) {
434 ++itr;
435 } else {
436 converted += absolute;
437 if (++itr != iend) {
438 converted += ";";
439 }
440 }
441 }
442 return converted;
443 }
444
445 // MG 05/10/09: When the Poco::FilePropertyConfiguration object reads its
446 // key/value pairs it
447 // treats a backslash as the start of an escape sequence. If the next
448 // character does not
449 // form a valid sequence then the backslash is removed from the stream. This
450 // has the effect
451 // of giving malformed paths when using Windows-style directories. E.g
452 // C:\Mantid ->C:Mantid
453 // std::filesystem::path can handle this better
454 bool is_relative(false);
455 try {
456 std::filesystem::path testPath(dir);
457 is_relative = testPath.is_relative();
458
459#ifdef _WIN32
460 // On Windows, std::filesystem treats paths starting with / or \ as relative
461 // (because they lack a drive letter), but we want to treat them as absolute
462 // to match the original Poco::Path behavior and Unix conventions
463 if (is_relative && !dir.empty() && (dir[0] == '/' || dir[0] == '\\')) {
464 is_relative = false;
465 }
466#endif
467 } catch (const std::exception &) {
468 g_log.warning() << "Malformed path detected in the \"" << key << "\" variable, skipping \"" << dir << "\"\n";
469 return "";
470 }
471 if (is_relative) {
472 const std::string propFileDir(getPropertiesDir());
473 std::filesystem::path basePath(propFileDir);
474 std::filesystem::path fullPath = basePath / dir;
475 converted = fullPath.string();
476 } else {
477 converted = dir;
478 }
479 std::filesystem::path convertedPath(converted);
480 if (!convertedPath.extension().empty()) {
481 converted = convertedPath.string();
482 } else {
483 // Ensure directory has trailing slash
484 converted = convertedPath.string();
485 if (converted.back() != '/' && converted.back() != '\\') {
486 converted += "/";
487 }
488 }
489 // Backward slashes cannot be allowed to go into our properties file
490 // Note this is a temporary fix for ticket #2445.
491 // Ticket #2460 prompts a review of our path handling in the config service.
492 boost::replace_all(converted, "\\", "/");
493 return converted;
494}
495
502 std::string paths = getString("datasearch.directories", true);
503 if (paths.empty()) {
504 m_dataSearchDirs.clear();
505 } else {
506 m_dataSearchDirs = splitPath(paths);
507 }
508}
509
516bool ConfigServiceImpl::isInDataSearchList(const std::string &path) const {
517 // the path produced by poco will have \ on windows, but the searchdirs will
518 // always have /
519 std::string correctedPath = path;
520 replace(correctedPath.begin(), correctedPath.end(), '\\', '/');
521
522 using std::placeholders::_1;
523 auto it = std::find_if(m_dataSearchDirs.cbegin(), m_dataSearchDirs.cend(),
524 std::bind(std::equal_to<std::string>(), _1, correctedPath));
525 return (it != m_dataSearchDirs.end());
526}
527
533 try {
534 std::fstream filestr((getUserPropertiesDir() + m_user_properties_file_name).c_str(), std::fstream::out);
535
536 filestr << "# This file can be used to override any properties for this "
537 "installation.\n";
538 filestr << "# Any properties found in this file will override any that are "
539 "found in the Mantid.Properties file\n";
540 filestr << "# As this file will not be replaced with further installations "
541 "of Mantid it is a safe place to put \n";
542 filestr << "# properties that suit your particular installation.\n";
543 filestr << "#\n";
544 filestr << "# See here for a list of possible options:\n";
545 filestr << "# "
546 "http://docs.mantidproject.org/nightly/concepts/PropertiesFile.html"
547 "\n\n";
548 filestr << "##\n";
549 filestr << "## GENERAL\n";
550 filestr << "##\n\n";
551 filestr << "## Set the maximum number of cores used to run algorithms over\n";
552 filestr << "#MultiThreaded.MaxCores=4\n\n";
553 filestr << "##\n";
554 filestr << "## FACILITY AND INSTRUMENT\n";
555 filestr << "##\n\n";
556 filestr << "## Sets the default facility\n";
557 filestr << "## e.g.: ISIS, SNS, ILL\n";
558 filestr << "default.facility=\n\n";
559 filestr << "## Sets the default instrument\n";
560 filestr << "## e.g. IRIS, HET, NIMROD\n";
561 filestr << "default.instrument=\n\n";
562 filestr << '\n';
563 filestr << "## Sets the Q.convention\n";
564 filestr << "## Set to Crystallography for kf-ki instead of default "
565 "Inelastic which is ki-kf\n";
566 filestr << "#Q.convention=Crystallography\n";
567 filestr << "##\n";
568 filestr << "## DIRECTORIES\n";
569 filestr << "##\n\n";
570 filestr << "## Sets a list of directories (separated by semi colons) to "
571 "search for data\n";
572 filestr << "#datasearch.directories=../data;../isis/data\n\n";
573 filestr << "## Set a list (separated by semi colons) of directories to "
574 "look for additional Python scripts\n";
575 filestr << "#pythonscripts.directories=../scripts;../docs/MyScripts\n\n";
576 filestr << "## Uncomment to enable archive search - ICat and Orbiter\n";
577 filestr << "#datasearch.searcharchive=On\n\n";
578 filestr << "## Sets default save directory\n";
579 filestr << "#defaultsave.directory=../data\n\n";
580 filestr << "##\n";
581 filestr << "## LOGGING\n";
582 filestr << "##\n\n";
583 filestr << "## Uncomment to change logging level\n";
584 filestr << "## Default is information\n";
585 filestr << "## Valid values are: error, warning, notice, information, debug\n";
586 filestr << "#logging.loggers.root.level=information\n\n";
587 filestr << "##\n";
588 filestr << "## MantidWorkbench\n";
589 filestr << "##\n\n";
590 filestr << "## Hides categories from the algorithm list in MantidWorkbench\n";
591 filestr << "#algorithms.catagories.hidden=Muons,Inelastic\n\n";
592 filestr << "## Show invisible workspaces\n";
593 filestr << "#MantidOptions.InvisibleWorkspaces=0\n";
594 filestr << "## Re-use plot instances for different plot types\n";
595 filestr << "#MantidOptions.ReusePlotInstances=Off\n\n";
596 filestr << "## Uncomment to disable use of OpenGL to render unwrapped "
597 "instrument views\n";
598 filestr << "#MantidOptions.InstrumentView.UseOpenGL=Off\n\n";
599 filestr << "## Muon GUI settings\n";
600 filestr << "#muon.GUI = \n";
601
602 filestr.close();
603 } catch (std::runtime_error &ex) {
604 g_log.warning() << "Unable to write out user.properties file to " << getUserPropertiesDir()
605 << m_user_properties_file_name << " error: " << ex.what() << '\n';
606 }
607}
608
609//-------------------------------
610// Public member functions
611//-------------------------------
612
617 // Remove the current user properties file and write a fresh one
618 try {
619 std::filesystem::remove(getUserFilename());
620 } catch (const std::exception &) {
621 }
623
624 // Now load the original
625 const bool append = false;
626 const bool updateCaches = true;
627 updateConfig(getPropertiesDir() + m_properties_file_name, append, updateCaches);
628}
629
639void ConfigServiceImpl::updateConfig(const std::string &filename, const bool append, const bool update_caches) {
640 loadConfig(filename, append);
641
642 if (update_caches) {
643 // Only configure logging once
645 // Configure search paths into a specially saved store as they will be used
646 // frequently
648 appendDataSearchDir(getString("defaultsave.directory"));
650 }
651}
652
658void ConfigServiceImpl::saveConfig(const std::string &filename) const {
659 // Open and read the user properties file
660 std::string updated_file;
661
662 std::ifstream reader(filename.c_str(), std::ios::in);
663 if (reader.bad()) {
664 throw std::runtime_error("Error opening user properties file. Cannot save "
665 "updated configuration.");
666 }
667
668 std::string file_line, output;
669 bool line_continuing(false);
670 while (std::getline(reader, file_line)) {
671 if (!file_line.empty()) {
672 char last = *(file_line.end() - 1);
673 if (last == '\\') {
674 // If we are not in line continuation mode then need
675 // a fresh start line
676 if (!line_continuing)
677 output = "";
678 line_continuing = true;
679 output += file_line + "\n";
680 continue;
681 } else if (line_continuing) {
682 output += file_line;
683 line_continuing = false;
684 } else {
685 output = file_line;
686 }
687 } else {
688 output = "";
689 updated_file += "\n";
690 continue;
691 } // end if-else
692
693 // Output is the current line in the file
694
695 // Extract the key from the current line
696 std::string key;
697 std::string::size_type pos = output.find('=');
698 if (pos == std::string::npos) {
699 key = output; // If no equals then the entire thing is the key
700 } else {
701 key = output.substr(0, pos); // Strip the equals to get only the key
702 }
703 // Now deal with trimming (removes spaces)
704 Poco::trimInPlace(key);
705
706 // Find the comments
707 std::string::size_type comment = key.find('#');
708
709 // Check if it exists in the service using hasProperty and make sure it
710 // isn't a comment
711 if (comment == 0) {
712 updated_file += output;
713 } else if (!hasProperty(key)) {
714 // Remove the key from the changed key list
715 m_changed_keys.erase(key);
716 continue;
717 } else {
718 // If it does exist make sure the value is current
719 std::string value = getString(key, false);
720 Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
721 updated_file.append(key).append("=").append(value);
722 // Remove the key from the changed key list
723 m_changed_keys.erase(key);
724 }
725 updated_file += "\n";
726 } // End while-loop
727
728 // Any remaining keys within the changed key store weren't present in the
729 // current user properties so append them IF they exist
730 if (!m_changed_keys.empty()) {
731 updated_file += "\n";
732 auto key_end = m_changed_keys.end();
733 for (auto key_itr = m_changed_keys.begin(); key_itr != key_end;) {
734 // if the key does not have a property, skip it
735 if (!hasProperty(*key_itr)) {
736 ++key_itr;
737 continue;
738 }
739 updated_file += *key_itr + "=";
740 std::string value = getString(*key_itr, false);
741 Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
742 updated_file += value;
743 if (++key_itr != key_end) {
744 updated_file += "\n";
745 }
746 }
747 m_changed_keys.clear();
748 }
749
750 // Write out the new file
751 std::ofstream writer(filename.c_str(), std::ios_base::trunc);
752 if (writer.bad()) {
753 writer.close();
754 g_log.error() << "Error writing new user properties file. Cannot save "
755 "current configuration.\n";
756 throw std::runtime_error("Error writing new user properties file. Cannot "
757 "save current configuration.");
758 }
759
760 writer.write(updated_file.c_str(), updated_file.size());
761 writer.close();
762}
763
776std::string ConfigServiceImpl::getString(const std::string &keyName, bool pathAbsolute) const {
777 if (m_pConf->hasProperty(keyName)) {
778 std::string value = m_pConf->getString(keyName);
779 const auto key = m_configPaths.find(keyName);
780 if (pathAbsolute && key != m_configPaths.end()) {
781 value = makeAbsolute(value, keyName);
782 }
783 return value;
784 }
785
786 g_log.debug() << "Unable to find " << keyName << " in the properties file" << '\n';
787 return {};
788}
789
799std::vector<std::string> ConfigServiceImpl::getKeys(const std::string &keyName) const {
800 std::vector<std::string> rawKeys;
801 m_pConf->keys(keyName, rawKeys);
802 return rawKeys;
803}
804
808void ConfigServiceImpl::getKeysRecursive(const std::string &root, std::vector<std::string> &allKeys) const {
809 std::vector<std::string> rootKeys = getKeys(root);
810
811 if (rootKeys.empty())
812 allKeys.emplace_back(root);
813
814 for (auto &rootKey : rootKeys) {
815 std::string searchString;
816 if (root.empty()) {
817 searchString.append(rootKey);
818 } else {
819 searchString.append(root).append(".").append(rootKey);
820 }
821
822 getKeysRecursive(searchString, allKeys);
823 }
824}
825
834std::vector<std::string> ConfigServiceImpl::keys() const {
835 std::vector<std::string> allKeys;
836 getKeysRecursive("", allKeys);
837 return allKeys;
838}
839
848void ConfigServiceImpl::remove(const std::string &rootName) {
849 m_pConf->remove(rootName);
850 m_changed_keys.insert(rootName);
851}
852
859bool ConfigServiceImpl::hasProperty(const std::string &rootName) const { return m_pConf->hasProperty(rootName); }
860
861namespace {
862std::string expandEnvironmentInFilepath(const std::string &target) { return Poco::Path::expand(target); }
863} // namespace
864
873bool ConfigServiceImpl::isExecutable(const std::string &target) const {
874 try {
875 std::filesystem::path filepath(expandEnvironmentInFilepath(target));
876
877 if (std::filesystem::exists(filepath) && std::filesystem::is_regular_file(filepath)) {
878 // On Unix-like systems, check execute permission
879 // std::filesystem doesn't have a direct equivalent to canExecute
880 // so we use std::filesystem::status
881#ifdef _WIN32
882 // On Windows, check if it's a regular file with .exe, .bat, .cmd extension
883 std::string ext = filepath.extension().string();
884 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
885 return (ext == ".exe" || ext == ".bat" || ext == ".cmd" || ext == ".com");
886#else
887 // On Unix-like systems, check execute permissions
888 auto perms = std::filesystem::status(filepath).permissions();
889 return (perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none ||
890 (perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none ||
891 (perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none;
892#endif
893 } else
894 return false;
895 } catch (const std::exception &) {
896 return false;
897 }
898}
899
912void ConfigServiceImpl::launchProcess(const std::string &programFilePath,
913 const std::vector<std::string> &programArguments) const {
914 try {
915 std::string expTarget = expandEnvironmentInFilepath(programFilePath);
916 Poco::Process::launch(expTarget, programArguments);
917 } catch (Poco::SystemException &e) {
918 throw std::runtime_error(e.what());
919 }
920}
921
927void ConfigServiceImpl::setString(const std::string &key, const std::string &value) {
928 // If the value is unchanged (after any path conversions), there's nothing to
929 // do.
930 const std::string old = getString(key);
931 if (value == old)
932 return;
933
934 // Update the internal value
935 m_pConf->setString(key, value);
936
937 // Cache things that are accessed frequently
938 if (key == "datasearch.directories") {
940 } else if (key == "instrumentDefinition.directory") {
942 } else if (key == "defaultsave.directory") {
944 } else if (key == "logging.channels.consoleChannel.class") {
945 // this key requires reloading logging for it to take effect
947 } else if (key == LOG_LEVEL_KEY) {
948 this->setLogLevel(value);
949 }
950
951 m_notificationCenter.postNotification(new ValueChanged(key, value, old));
952 m_changed_keys.insert(key);
953}
954
962template <typename T> std::optional<T> ConfigServiceImpl::getValue(const std::string &keyName) {
963 std::string strValue = getString(keyName);
964 T output;
965 int result = Mantid::Kernel::Strings::convert(strValue, output);
966
967 if (result != 1) {
968 return std::nullopt;
969 }
970
971 return std::optional<T>(output);
972}
973
981template <> std::optional<bool> ConfigServiceImpl::getValue(const std::string &keyName) {
982 auto returnedValue = getValue<std::string>(keyName);
983 if (!returnedValue.has_value()) {
984 return std::nullopt;
985 }
986
987 auto &configVal = returnedValue.value();
988
989 std::transform(configVal.begin(), configVal.end(), configVal.begin(), ::tolower);
990
991 boost::trim(configVal);
992
993 bool trueString = configVal == "true";
994 bool valueOne = configVal == "1";
995 bool onOffString = configVal == "on";
996
997 // A string of 1 or true both count
998 return trueString || valueOne || onOffString;
999}
1000
1006#ifdef _WIN32
1007 return "Mantid.local.properties";
1008#else
1009 return "/etc/mantid.local.properties";
1010#endif
1011}
1012
1018
1026std::string ConfigServiceImpl::getEnvironment(const std::string &keyName) {
1027 return m_pSysConfig->getString("system.env." + keyName);
1028}
1029
1034std::string ConfigServiceImpl::getOSName() { return m_pSysConfig->getString("system.osName"); }
1035
1041 auto osArch = m_pSysConfig->getString("system.osArchitecture");
1042#ifdef __APPLE__
1043 if (osArch == "x86_64") {
1044 // This could be running under Rosetta on an Arm Mac
1045 // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment
1046 int ret = 0;
1047 size_t size = sizeof(ret);
1048 if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) != -1 && ret == 1) {
1049 osArch = "arm64_(x86_64)";
1050 g_log.warning("You are running an Intel build of Mantid on Apple silicon, which will be significantly slower and "
1051 "use more power. For best performance, install the Arm version of Mantid. This version is "
1052 "available here: https://downloads.mantidproject.org");
1053 } else {
1054 // TODO this can be removed after the v6.14 code freeze because the feature will be dropped
1055 g_log.warning("Mantid v6.14 is the last version that will support Intel macOS.");
1056 }
1057 }
1058#endif
1059 return osArch;
1060}
1061
1066std::string ConfigServiceImpl::getComputerName() { return m_pSysConfig->getString("system.nodeName"); }
1067
1072std::string ConfigServiceImpl::getOSVersion() { return m_pSysConfig->getString("system.osVersion"); }
1073
1075bool canRead(const std::string &filename) {
1076 // check for existence of the file
1077 if (!std::filesystem::exists(filename)) {
1078 return false;
1079 }
1080
1081 // Try to open the file to check if it's readable
1082 std::ifstream file(filename);
1083 return file.good();
1084}
1085
1087std::string getValueFromStdOut(const std::string &orig, const std::string &key) {
1088 size_t start = orig.find(key);
1089 if (start == std::string::npos) {
1090 return std::string();
1091 }
1092 start += key.size();
1093
1094 size_t stop = orig.find('\n', start);
1095 if (stop == std::string::npos) {
1096 return std::string();
1097 }
1098
1099 return Mantid::Kernel::Strings::strip(orig.substr(start, stop - start));
1100}
1101
1108 std::string description;
1109
1110 // read os-release
1111 static const std::string OS_RELEASE("/etc/os-release");
1112 if (canRead(OS_RELEASE)) {
1113 static const std::string PRETTY_NAME("PRETTY_NAME=");
1114
1115 // open it to see if it has the magic line
1116 std::ifstream handle(OS_RELEASE.c_str(), std::ios::in);
1117
1118 // go through the file
1119 std::string line;
1120 while (std::getline(handle, line)) {
1121 if (line.find(PRETTY_NAME) != std::string::npos) {
1122 if (line.length() > PRETTY_NAME.length() + 1) {
1123 size_t length = line.length() - PRETTY_NAME.length() - 2;
1124 description = line.substr(PRETTY_NAME.length() + 1, length);
1125 }
1126 break;
1127 }
1128 }
1129
1130 // cleanup
1131 handle.close();
1132 if (!description.empty()) {
1133 return description;
1134 }
1135 }
1136
1137 // read redhat-release
1138 static const std::string REDHAT_RELEASE("/etc/redhat-release");
1139 if (canRead(REDHAT_RELEASE)) {
1140 // open it to see if it has the magic line
1141 std::ifstream handle(REDHAT_RELEASE.c_str(), std::ios::in);
1142
1143 // go through the file
1144 std::string line;
1145 while (std::getline(handle, line)) {
1146 if (!line.empty()) {
1147 description = std::move(line);
1148 break;
1149 }
1150 }
1151
1152 // cleanup
1153 handle.close();
1154 if (!description.empty()) {
1155 return description;
1156 }
1157 }
1158
1159 // try system calls
1160 std::string cmd;
1161 std::vector<std::string> args;
1162#ifdef __APPLE__
1163 cmd = "sw_vers"; // mac
1164#elif _WIN32
1165 cmd = "wmic"; // windows
1166 args.emplace_back("os"); // windows
1167 args.emplace_back("get"); // windows
1168 args.emplace_back("Caption"); // windows
1169 args.emplace_back("/value"); // windows
1170#endif
1171
1172#if defined __APPLE__ || defined _WIN32
1173 try {
1174 Poco::Pipe outPipe, errorPipe;
1175 Poco::ProcessHandle ph = Poco::Process::launch(cmd, args, nullptr, &outPipe, &errorPipe);
1176 const int rc = ph.wait();
1177 // Only if the command returned successfully.
1178 if (rc == 0) {
1179 Poco::PipeInputStream pipeStream(outPipe);
1180 std::stringstream stringStream;
1181 Poco::StreamCopier::copyStream(pipeStream, stringStream);
1182 const std::string result = stringStream.str();
1183#ifdef __APPLE__
1184 const std::string product_name = getValueFromStdOut(result, "ProductName:");
1185 const std::string product_vers = getValueFromStdOut(result, "ProductVersion:");
1186
1187 description = product_name + " " + product_vers;
1188#elif _WIN32
1189 description = getValueFromStdOut(result, "Caption=");
1190#else
1191 UNUSED_ARG(result); // only used on mac and windows
1192#endif
1193 } else {
1194 std::stringstream messageStream;
1195 messageStream << "command \"" << cmd << "\" failed with code: " << rc;
1196 g_log.debug(messageStream.str());
1197 }
1198 } catch (Poco::SystemException &e) {
1199 g_log.debug("command \"" + cmd + "\" failed");
1200 g_log.debug(e.what());
1201 }
1202#endif
1203 return description;
1204}
1205
1208 std::string username;
1209
1210 // mac and favorite way to get username on linux
1211 try {
1212 username = m_pSysConfig->getString("system.env.USER");
1213 if (!username.empty()) {
1214 return username;
1215 }
1216 } catch (const Poco::NotFoundException &e) {
1217 UNUSED_ARG(e); // let it drop on the floor
1218 }
1219
1220 // windoze and alternate linux username variable
1221 try {
1222 username = m_pSysConfig->getString("system.env.USERNAME");
1223 if (!username.empty()) {
1224 return username;
1225 }
1226 } catch (const Poco::NotFoundException &e) {
1227 UNUSED_ARG(e); // let it drop on the floor
1228 }
1229
1230 // give up and return an empty string
1231 return std::string();
1232}
1233
1238std::string ConfigServiceImpl::getCurrentDir() { return m_pSysConfig->getString("system.currentDir"); }
1239
1245std::string ConfigServiceImpl::getCurrentDir() const { return m_pSysConfig->getString("system.currentDir"); }
1246
1251std::string ConfigServiceImpl::getTempDir() { return m_pSysConfig->getString("system.tempDir"); }
1252
1258 const std::string applicationName = "mantid";
1259#if POCO_OS == POCO_OS_WINDOWS_NT
1260 const std::string vendorName = "mantidproject";
1261 wchar_t *w_appdata = _wgetenv(L"APPDATA");
1262 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
1263 std::string appdata = converter.to_bytes(w_appdata);
1264 std::filesystem::path path(appdata);
1265 path /= vendorName;
1266 path /= applicationName;
1267 return path.string();
1268#else // linux and mac
1269 const char *home = std::getenv("HOME");
1270 if (!home) {
1271 throw std::runtime_error("HOME environment variable not set - seen in ConfigService.getAppDataDir()");
1272 }
1273 std::filesystem::path path(home);
1274 path /= ("." + applicationName);
1275 return path.string();
1276#endif
1277}
1278
1285 std::filesystem::path execPath(getPathToExecutable());
1286 return execPath.parent_path().string() + "/";
1287}
1288
1295 std::string execpath;
1296 const size_t LEN(1024);
1297 char pBuf[LEN];
1298
1299#ifdef _WIN32
1300 unsigned int bytes = GetModuleFileName(NULL, pBuf, LEN);
1301#elif defined __linux__
1302 char szTmp[32];
1303 sprintf(szTmp, "/proc/%d/exe", getpid());
1304 ssize_t bytes = readlink(szTmp, pBuf, LEN);
1305#elif defined __APPLE__
1306 // Two calls to _NSGetExecutablePath required - first to get size of buffer
1307 uint32_t bytes(0);
1308 _NSGetExecutablePath(pBuf, &bytes);
1309 const int success = _NSGetExecutablePath(pBuf, &bytes);
1310 if (success < 0)
1311 bytes = 1025;
1312#endif
1313
1314 if (bytes > 0 && bytes < 1024) {
1315 pBuf[bytes] = '\0';
1316 execpath = std::string(pBuf);
1317 }
1318 return execpath;
1319}
1320
1326bool ConfigServiceImpl::isNetworkDrive([[maybe_unused]] const std::string &path) {
1327#ifdef _WIN32
1328 // if path is relative get the full one
1329 char buff[MAX_PATH];
1330 GetFullPathName(path.c_str(), MAX_PATH, buff, NULL);
1331 std::string fullName(buff);
1332 size_t i = fullName.find(':');
1333
1334 // if the full path doesn't contain a drive letter assume it's on the network
1335 if (i == std::string::npos)
1336 return true;
1337
1338 fullName.erase(i + 1);
1339 fullName += '\\'; // make sure the name has the trailing backslash
1340 UINT type = GetDriveType(fullName.c_str());
1341 return DRIVE_REMOTE == type;
1342#elif defined __linux__
1343 // This information is only present in the /proc/mounts file on linux. There
1344 // are no drives on
1345 // linux only mount locations therefore the test will have to check the path
1346 // against
1347 // entries in /proc/mounts to see if the filesystem type is NFS or SMB (any
1348 // others ????)
1349 // Each line corresponds to a particular mounted location
1350 // 1st column - device name
1351 // 2nd column - mounted location
1352 // 3rd column - filesystem type commonly ext2, ext3 for hard drives and NFS or
1353 // SMB for
1354 // network locations
1355
1356 std::ifstream mntfile("/proc/mounts");
1357 std::string txtread("");
1358 while (getline(mntfile, txtread)) {
1359 std::istringstream strm(txtread);
1360 std::string devname(""), mntpoint(""), fstype("");
1361 strm >> devname >> mntpoint >> fstype;
1362 if (!strm)
1363 continue;
1364 // I can't be sure that the file system type is always lower case
1365 std::transform(fstype.begin(), fstype.end(), fstype.begin(), toupper);
1366 // Skip the current line if the file system isn't a network one
1367 if (fstype != "NFS" && fstype != "SMB")
1368 continue;
1369 // Now we have a line containing a network filesystem and just need to check
1370 // if the path
1371 // supplied contains the mount location. There is a small complication in
1372 // that the mount
1373 // points within the file have certain characters transformed into their
1374 // octal
1375 // representations, for example spaces->040.
1376 std::string::size_type idx = mntpoint.find("\\0");
1377 if (idx != std::string::npos) {
1378 std::string oct = mntpoint.substr(idx + 1, 3);
1379 strm.str(oct);
1380 int printch(-1);
1381 strm.setf(std::ios::oct, std::ios::basefield);
1382 strm >> printch;
1383 if (printch != -1) {
1384 mntpoint = mntpoint.substr(0, idx) + static_cast<char>(printch) + mntpoint.substr(idx + 4);
1385 }
1386 // Search for this at the start of the path
1387 if (path.find(mntpoint) == 0)
1388 return true;
1389 }
1390 }
1391 return false;
1392#else
1393 // Not yet implemented for the mac
1394 return false;
1395#endif
1396}
1397
1408
1417#ifdef _WIN32
1418 return m_strBaseDir;
1419#else
1420 std::filesystem::path datadir(m_pSysConfig->getString("system.homeDir"));
1421 datadir /= ".mantid";
1422 // Create the directory if it doesn't already exist
1423 std::filesystem::create_directory(datadir);
1424 return datadir.string() + "/";
1425#endif
1426}
1427
1432const std::vector<std::string> &ConfigServiceImpl::getDataSearchDirs() const { return m_dataSearchDirs; }
1433
1438void ConfigServiceImpl::setDataSearchDirs(const std::vector<std::string> &searchDirs) {
1439 std::string searchPaths = boost::join(searchDirs, ";");
1440 setDataSearchDirs(searchPaths);
1441}
1442
1448void ConfigServiceImpl::setDataSearchDirs(const std::string &searchDirs) {
1449 setString("datasearch.directories", searchDirs);
1450}
1451
1457void ConfigServiceImpl::appendDataSearchSubDir(const std::string &subdir) {
1458 if (subdir.empty())
1459 return;
1460
1461 std::filesystem::path subDirPath;
1462 try {
1463 subDirPath = std::filesystem::path(subdir);
1464 } catch (const std::exception &) {
1465 return;
1466 }
1467
1468 // Check if path has trailing slash (indicates directory) and is relative
1469 if (!subDirPath.is_relative()) {
1470 return;
1471 }
1472
1473 auto newDataDirs = m_dataSearchDirs;
1474 for (const auto &path : m_dataSearchDirs) {
1475 std::filesystem::path newDirPath;
1476 try {
1477 newDirPath = std::filesystem::path(path) / subDirPath;
1478 // only add new path if it isn't already there
1479 if (std::find(newDataDirs.begin(), newDataDirs.end(), newDirPath.string()) == newDataDirs.end())
1480 newDataDirs.emplace_back(newDirPath.string());
1481 } catch (const std::exception &) {
1482 continue;
1483 }
1484 }
1485
1486 setDataSearchDirs(newDataDirs);
1487}
1488
1494void ConfigServiceImpl::appendDataSearchDir(const std::string &path) {
1495 if (path.empty())
1496 return;
1497
1498 std::filesystem::path dirPath;
1499 try {
1500 dirPath = std::filesystem::path(path);
1501 // Ensure it has a trailing slash for consistency
1502 std::string pathStr = dirPath.string();
1503 if (pathStr.back() != '/' && pathStr.back() != '\\') {
1504 pathStr += "/";
1505 }
1506 if (!isInDataSearchList(pathStr)) {
1507 auto newSearchString = Strings::join(std::begin(m_dataSearchDirs), std::end(m_dataSearchDirs), ";");
1508 newSearchString.append(";" + path);
1509 setString("datasearch.directories", newSearchString);
1510 }
1511 } catch (const std::exception &) {
1512 return;
1513 }
1514}
1515
1520void ConfigServiceImpl::setInstrumentDirectories(const std::vector<std::string> &directories) {
1521 m_instrumentDirs = directories;
1522}
1523
1528const std::vector<std::string> &ConfigServiceImpl::getInstrumentDirectories() const { return m_instrumentDirs; }
1529
1534const std::string ConfigServiceImpl::getInstrumentDirectory() const { return m_instrumentDirs.back(); }
1540 // Determine the search directory for XML instrument definition files (IDFs)
1541 std::string directoryName = getString("instrumentDefinition.vtp.directory");
1542
1543 if (directoryName.empty()) {
1544 std::filesystem::path path(getAppDataDir());
1545 path /= "instrument";
1546 path /= "geometryCache";
1547 directoryName = path.string();
1548 }
1549 return directoryName;
1550}
1562 m_instrumentDirs.clear();
1563
1564 std::filesystem::path path(getAppDataDir());
1565 path /= "instrument";
1566 const std::string appdatadir = path.string();
1568
1569#ifndef _WIN32
1570 addDirectoryifExists("/etc/mantid/instrument", m_instrumentDirs);
1571#endif
1572
1573 // Determine the search directory for XML instrument definition files (IDFs)
1574 std::string directoryName = getString("instrumentDefinition.directory", true);
1575 if (directoryName.empty()) {
1576 // This is the assumed deployment directory for IDFs, where we need to be
1577 // relative to the
1578 // directory of the executable, not the current working directory.
1579 std::filesystem::path basePath(getPropertiesDir());
1580 directoryName = (basePath / ".." / "instrument").lexically_normal().string();
1581 }
1582 addDirectoryifExists(directoryName, m_instrumentDirs);
1583}
1584
1592bool ConfigServiceImpl::addDirectoryifExists(const std::string &directoryName,
1593 std::vector<std::string> &directoryList) {
1594 try {
1595 if (std::filesystem::is_directory(directoryName)) {
1596 directoryList.emplace_back(directoryName);
1597 return true;
1598 } else {
1599 g_log.information("Unable to locate directory at: " + directoryName);
1600 return false;
1601 }
1602 } catch (const std::filesystem::filesystem_error &) {
1603 g_log.information("Unable to locate directory at: " + directoryName);
1604 return false;
1605 } catch (const std::exception &) {
1606 g_log.information("Unable to locate directory at: " + directoryName);
1607 return false;
1608 }
1609}
1610
1611const std::vector<std::string> ConfigServiceImpl::getFacilityFilenames(const std::string &fName) {
1612 std::vector<std::string> returnPaths;
1613
1614 // first try the supplied file
1615 if (!fName.empty()) {
1616 if (std::filesystem::exists(fName)) {
1617 returnPaths.emplace_back(fName);
1618 return returnPaths;
1619 }
1620 }
1621
1622 // search all of the instrument directories
1623 const auto &directoryNames = getInstrumentDirectories();
1624
1625 // only use downloaded instruments if configured to download
1626 const std::string updateInstrStr = this->getString("UpdateInstrumentDefinitions.OnStartup");
1627
1628 auto instrDir = directoryNames.begin();
1629
1630 // If we are not updating the instrument definitions
1631 // update the iterator, this means we will skip the folder in HOME and
1632 // look in the instrument folder in mantid install directory or mantid source
1633 // code directory
1634 if (!(updateInstrStr == "1" || updateInstrStr == "on" || updateInstrStr == "On") && directoryNames.size() > 1) {
1635 instrDir++;
1636 }
1637
1638 // look through all the possible files
1639 for (; instrDir != directoryNames.end(); ++instrDir) {
1640 std::filesystem::path p(*instrDir);
1641 p /= "Facilities.xml";
1642 std::string filename = p.string();
1643
1644 if (std::filesystem::exists(filename))
1645 returnPaths.emplace_back(filename);
1646 }
1647
1648 if (returnPaths.size() > 0) {
1649 return returnPaths;
1650 }
1651
1652 // getting this far means the file was not found
1653 std::string directoryNamesList = boost::algorithm::join(directoryNames, ", ");
1654 throw std::runtime_error("Failed to find \"Facilities.xml\". Searched in " + directoryNamesList);
1655}
1656
1667void ConfigServiceImpl::updateFacilities(const std::string &fName) {
1669
1670 // Try to find the file. If it does not exist we will crash, and cannot read
1671 // the Facilities file
1672 const auto fileNames = getFacilityFilenames(fName);
1673 size_t attemptIndex = 0;
1674 bool success = false;
1675 while ((!success) && (attemptIndex < fileNames.size())) {
1676 const auto &fileName = fileNames[attemptIndex];
1677 try {
1678 // Set up the DOM parser and parse xml file
1679 Poco::AutoPtr<Poco::XML::Document> pDoc;
1680 try {
1681 Poco::XML::DOMParser pParser;
1682 pDoc = pParser.parse(fileName);
1683 } catch (...) {
1684 throw Kernel::Exception::FileError("Unable to parse file:", fileName);
1685 }
1686
1687 // Get pointer to root element
1688 Poco::XML::Element *pRootElem = pDoc->documentElement();
1689 if (!pRootElem->hasChildNodes()) {
1690 throw std::runtime_error("No root element in Facilities.xml file");
1691 }
1692
1693 const Poco::AutoPtr<Poco::XML::NodeList> pNL_facility = pRootElem->getElementsByTagName("facility");
1694 const size_t n = pNL_facility->length();
1695
1696 for (unsigned long i = 0; i < n; ++i) {
1697 const auto *elem = dynamic_cast<Poco::XML::Element *>(pNL_facility->item(i));
1698 if (elem) {
1699 m_facilities.emplace_back(new FacilityInfo(elem));
1700 }
1701 }
1702
1703 if (m_facilities.empty()) {
1704 throw std::runtime_error("The facility definition file " + fileName + " defines no facilities");
1705 }
1706
1707 // if we got here we have suceeded and can exit the loop
1708 success = true;
1709 } catch (std::runtime_error &ex) {
1710 // log this failure to load a file
1711 g_log.error() << "Failed to load the facilities.xml file at " << fileName << "\nIt might be corrupt. "
1712 << ex.what() << "\nWill try to load another version.\n";
1713 attemptIndex++;
1714 // move on to the next file index if available
1715 if (attemptIndex == fileNames.size()) {
1716 const std::string errorMessage = "No more Facilities.xml files can be found, Mantid will not be "
1717 "able to start, Sorry. Try reinstalling Mantid.";
1718 // This is one of the few times that both logging a messge and throwing
1719 // might make sense
1720 // as the error reporter tends to swallow the thrown message.
1721 g_log.error() << errorMessage << "\n";
1722 // Throw an exception as we have run out of files to try
1723 throw std::runtime_error(errorMessage);
1724 }
1725 }
1726 }
1727}
1728
1732 for (const auto &facility : m_facilities) {
1733 delete facility;
1734 }
1735 m_facilities.clear();
1736}
1737
1744const InstrumentInfo &ConfigServiceImpl::getInstrument(const std::string &instrumentName) const {
1745
1746 // Let's first search for the instrument in our default facility
1747 std::string defaultFacility = getFacility().name();
1748
1749 if (!defaultFacility.empty()) {
1750 try {
1751 g_log.debug() << "Looking for " << instrumentName << " at " << defaultFacility << ".\n";
1752 return getFacility(defaultFacility).instrument(instrumentName);
1753 } catch (Exception::NotFoundError &) {
1754 // Well the instName doesn't exist for this facility
1755 // Move along, there's nothing to see here...
1756 }
1757 }
1758
1759 // Now let's look through the other facilities
1760 for (auto facility : m_facilities) {
1761 try {
1762 g_log.debug() << "Looking for " << instrumentName << " at " << (*facility).name() << ".\n";
1763 return (*facility).instrument(instrumentName);
1764 } catch (Exception::NotFoundError &) {
1765 // Well the instName doesn't exist for this facility...
1766 // Move along, there's nothing to see here...
1767 }
1768 }
1769
1770 const std::string errMsg = "Failed to find an instrument with this name in any facility: '" + instrumentName + "' -";
1771 g_log.debug("Instrument " + instrumentName + " not found");
1772 throw Exception::NotFoundError(errMsg, instrumentName);
1773}
1774
1778const std::vector<FacilityInfo *> ConfigServiceImpl::getFacilities() const { return m_facilities; }
1779
1783const std::vector<std::string> ConfigServiceImpl::getFacilityNames() const {
1784 auto names = std::vector<std::string>(m_facilities.size());
1785 std::transform(m_facilities.cbegin(), m_facilities.cend(), names.begin(),
1786 [](const FacilityInfo *facility) { return facility->name(); });
1787
1788 return names;
1789}
1790
1795 std::string defFacility = getString("default.facility");
1796 if (defFacility.empty()) {
1797 defFacility = " ";
1798 }
1799 return this->getFacility(defFacility);
1800}
1801
1808const FacilityInfo &ConfigServiceImpl::getFacility(const std::string &facilityName) const {
1809 if (facilityName.empty())
1810 return this->getFacility();
1811
1812 auto facility = std::find_if(m_facilities.cbegin(), m_facilities.cend(),
1813 [&facilityName](const auto f) { return f->name() == facilityName; });
1814
1815 if (facility != m_facilities.cend()) {
1816 return **facility;
1817 }
1818
1819 throw Exception::NotFoundError("Facilities", facilityName);
1820}
1821
1827void ConfigServiceImpl::setFacility(const std::string &facilityName) {
1828 const FacilityInfo *foundFacility = nullptr;
1829
1830 try {
1831 // Get facility looks up by string - so re-use that to check if the facility
1832 // is known
1833 foundFacility = &getFacility(facilityName);
1834 } catch (const Exception::NotFoundError &) {
1835 g_log.error("Failed to set default facility to be " + facilityName + ". Facility not found");
1836 throw;
1837 }
1838 assert(foundFacility);
1839 setString("default.facility", facilityName);
1840
1841 const auto &associatedInsts = foundFacility->instruments();
1842 if (associatedInsts.empty()) {
1843 throw std::invalid_argument("The selected facility has no instruments associated with it");
1844 }
1845
1846 // Update the default instrument to be one from this facility
1847 setString("default.instrument", associatedInsts[0].name());
1848}
1849
1853void ConfigServiceImpl::addObserver(const Poco::AbstractObserver &observer) const {
1854 m_notificationCenter.addObserver(observer);
1855}
1856
1860void ConfigServiceImpl::removeObserver(const Poco::AbstractObserver &observer) const {
1861 m_notificationCenter.removeObserver(observer);
1862}
1863
1864/*
1865Gets the system proxy information
1866@url A url to match the proxy to
1867@return the proxy information.
1868*/
1870 if (!m_isProxySet) {
1871 // set the proxy
1872 // first check if the proxy is defined in the properties file
1873 auto proxyHost = getValue<std::string>("proxy.host");
1874 auto proxyPort = getValue<int>("proxy.port");
1875
1876 if (proxyHost.has_value() && proxyPort.has_value()) {
1877 // set it from the config values
1878 m_proxyInfo = ProxyInfo(proxyHost.value(), proxyPort.value(), true);
1879 } else {
1880 // get the system proxy
1881 Poco::URI uri(url);
1882 Mantid::Kernel::NetworkProxy proxyHelper;
1883 m_proxyInfo = proxyHelper.getHttpProxy(uri.toString());
1884 }
1885 m_isProxySet = true;
1886 }
1887 return m_proxyInfo;
1888}
1889
1890std::string ConfigServiceImpl::getFullPath(const std::string &filename, const bool ignoreDirs,
1891 const int options) const {
1892 std::string fName = Kernel::Strings::strip(filename);
1893 g_log.debug() << "getFullPath(" << fName << ")\n";
1894 // If this is already a full path, nothing to do
1895 std::filesystem::path filepath(fName);
1896 if (filepath.is_absolute())
1897 return fName;
1898 // First try the path relative to the current directory. Can throw in some
1899 // circumstances with extensions that have wild cards
1900 try {
1901 std::filesystem::path fullPath = std::filesystem::current_path() / fName;
1902 if (std::filesystem::exists(fullPath) && (!ignoreDirs || !std::filesystem::is_directory(fullPath)))
1903 return fullPath.string();
1904 } catch (const std::exception &) {
1905 }
1906
1907 auto directoryNames = getDataSearchDirs();
1908 const auto &instrDirectories = getInstrumentDirectories();
1909 directoryNames.insert(directoryNames.end(), instrDirectories.begin(), instrDirectories.end());
1910 for (const auto &searchPath : directoryNames) {
1911 g_log.debug() << "Searching for " << fName << " in " << searchPath << "\n";
1912// On windows globbing is not working properly with network drives
1913// for example a network drive containing a $
1914// For this reason, and since windows is case insensitive anyway
1915// a special case is made for windows
1916#ifdef _WIN32
1917 if (fName.find("*") != std::string::npos) {
1918#endif
1919 std::filesystem::path path = std::filesystem::path(searchPath) / fName;
1920 std::set<std::string> files;
1921 Kernel::Glob::glob(path.string(), files, options);
1922 if (!files.empty()) {
1923 std::filesystem::path matchPath(*files.begin());
1924 if (ignoreDirs && std::filesystem::is_directory(matchPath)) {
1925 continue;
1926 }
1927 return *files.begin();
1928 }
1929#ifdef _WIN32
1930 } else {
1931 std::filesystem::path path = std::filesystem::path(searchPath) / fName;
1932 if (std::filesystem::exists(path) && !(ignoreDirs && std::filesystem::is_directory(path))) {
1933 return path.string();
1934 }
1935 }
1936#endif
1937 }
1938 return "";
1939}
1940
1946void ConfigServiceImpl::setLogLevel(int logLevel, bool quiet) {
1948 // update the internal value to keep strings in sync
1949 m_pConf->setString(LOG_LEVEL_KEY, g_log.getLevelName());
1950
1951 if (!quiet) {
1952 g_log.log("logging set to " + Logger::PriorityNames[std::size_t(logLevel)] + " priority",
1953 static_cast<Logger::Priority>(logLevel));
1954 }
1955}
1956
1957void ConfigServiceImpl::setLogLevel(std::string const &logLevel, bool quiet) {
1959 // update the internal value to keep strings in sync
1960 m_pConf->setString(LOG_LEVEL_KEY, g_log.getLevelName());
1961
1962 if (!quiet) {
1963 g_log.log("logging set to " + logLevel + " priority", static_cast<Logger::Priority>(g_log.getLevel()));
1964 }
1965}
1966
1968
1970template DLLExport std::optional<double> ConfigServiceImpl::getValue(const std::string &);
1971template DLLExport std::optional<std::string> ConfigServiceImpl::getValue(const std::string &);
1972template DLLExport std::optional<int> ConfigServiceImpl::getValue(const std::string &);
1973template DLLExport std::optional<size_t> ConfigServiceImpl::getValue(const std::string &);
1974#ifdef _MSC_VER
1975template DLLExport std::optional<bool> ConfigServiceImpl::getValue(const std::string &);
1976#endif
1977
1979
1980} // namespace Kernel
1981} // 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 char * version()
The full version number.
static std::string doi()
The DOI for this release of Mantid.
static std::string paperCitation()
The citation for the Mantid paper.
static const char * revision()
The abbreviated SHA-1 of the last commit.
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.