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 // 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\n";
601 filestr << "## SANS ISIS Command Interface\n";
602 filestr << "## Uncomment below line to allow usage of deprecated ISIS Command Interface\n";
603 filestr << "#sans.deprecated_command_interface=On\n\n";
604
605 filestr.close();
606 } catch (std::runtime_error &ex) {
607 g_log.warning() << "Unable to write out user.properties file to " << getUserPropertiesDir()
608 << m_user_properties_file_name << " error: " << ex.what() << '\n';
609 }
610}
611
612//-------------------------------
613// Public member functions
614//-------------------------------
615
620 // Remove the current user properties file and write a fresh one
621 try {
622 std::filesystem::remove(getUserFilename());
623 } catch (const std::exception &) {
624 }
626
627 // Now load the original
628 const bool append = false;
629 const bool updateCaches = true;
630 updateConfig(getPropertiesDir() + m_properties_file_name, append, updateCaches);
631}
632
642void ConfigServiceImpl::updateConfig(const std::string &filename, const bool append, const bool update_caches) {
643 loadConfig(filename, append);
644
645 if (update_caches) {
646 // Only configure logging once
648 // Configure search paths into a specially saved store as they will be used
649 // frequently
651 appendDataSearchDir(getString("defaultsave.directory"));
653 }
654}
655
661void ConfigServiceImpl::saveConfig(const std::string &filename) const {
662 // Open and read the user properties file
663 std::string updated_file;
664
665 std::ifstream reader(filename.c_str(), std::ios::in);
666 if (reader.bad()) {
667 throw std::runtime_error("Error opening user properties file. Cannot save "
668 "updated configuration.");
669 }
670
671 std::string file_line, output;
672 bool line_continuing(false);
673 while (std::getline(reader, file_line)) {
674 if (!file_line.empty()) {
675 char last = *(file_line.end() - 1);
676 if (last == '\\') {
677 // If we are not in line continuation mode then need
678 // a fresh start line
679 if (!line_continuing)
680 output = "";
681 line_continuing = true;
682 output += file_line + "\n";
683 continue;
684 } else if (line_continuing) {
685 output += file_line;
686 line_continuing = false;
687 } else {
688 output = file_line;
689 }
690 } else {
691 output = "";
692 updated_file += "\n";
693 continue;
694 } // end if-else
695
696 // Output is the current line in the file
697
698 // Extract the key from the current line
699 std::string key;
700 std::string::size_type pos = output.find('=');
701 if (pos == std::string::npos) {
702 key = output; // If no equals then the entire thing is the key
703 } else {
704 key = output.substr(0, pos); // Strip the equals to get only the key
705 }
706 // Now deal with trimming (removes spaces)
707 Poco::trimInPlace(key);
708
709 // Find the comments
710 std::string::size_type comment = key.find('#');
711
712 // Check if it exists in the service using hasProperty and make sure it
713 // isn't a comment
714 if (comment == 0) {
715 updated_file += output;
716 } else if (!hasProperty(key)) {
717 // Remove the key from the changed key list
718 m_changed_keys.erase(key);
719 continue;
720 } else {
721 // If it does exist make sure the value is current
722 std::string value = getString(key, false);
723 Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
724 updated_file.append(key).append("=").append(value);
725 // Remove the key from the changed key list
726 m_changed_keys.erase(key);
727 }
728 updated_file += "\n";
729 } // End while-loop
730
731 // Any remaining keys within the changed key store weren't present in the
732 // current user properties so append them IF they exist
733 if (!m_changed_keys.empty()) {
734 updated_file += "\n";
735 auto key_end = m_changed_keys.end();
736 for (auto key_itr = m_changed_keys.begin(); key_itr != key_end;) {
737 // if the key does not have a property, skip it
738 if (!hasProperty(*key_itr)) {
739 ++key_itr;
740 continue;
741 }
742 updated_file += *key_itr + "=";
743 std::string value = getString(*key_itr, false);
744 Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
745 updated_file += value;
746 if (++key_itr != key_end) {
747 updated_file += "\n";
748 }
749 }
750 m_changed_keys.clear();
751 }
752
753 // Write out the new file
754 std::ofstream writer(filename.c_str(), std::ios_base::trunc);
755 if (writer.bad()) {
756 writer.close();
757 g_log.error() << "Error writing new user properties file. Cannot save "
758 "current configuration.\n";
759 throw std::runtime_error("Error writing new user properties file. Cannot "
760 "save current configuration.");
761 }
762
763 writer.write(updated_file.c_str(), updated_file.size());
764 writer.close();
765}
766
779std::string ConfigServiceImpl::getString(const std::string &keyName, bool pathAbsolute) const {
780 if (m_pConf->hasProperty(keyName)) {
781 std::string value = m_pConf->getString(keyName);
782 const auto key = m_configPaths.find(keyName);
783 if (pathAbsolute && key != m_configPaths.end()) {
784 value = makeAbsolute(value, keyName);
785 }
786 return value;
787 }
788
789 g_log.debug() << "Unable to find " << keyName << " in the properties file" << '\n';
790 return {};
791}
792
802std::vector<std::string> ConfigServiceImpl::getKeys(const std::string &keyName) const {
803 std::vector<std::string> rawKeys;
804 m_pConf->keys(keyName, rawKeys);
805 return rawKeys;
806}
807
811void ConfigServiceImpl::getKeysRecursive(const std::string &root, std::vector<std::string> &allKeys) const {
812 std::vector<std::string> rootKeys = getKeys(root);
813
814 if (rootKeys.empty())
815 allKeys.emplace_back(root);
816
817 for (auto &rootKey : rootKeys) {
818 std::string searchString;
819 if (root.empty()) {
820 searchString.append(rootKey);
821 } else {
822 searchString.append(root).append(".").append(rootKey);
823 }
824
825 getKeysRecursive(searchString, allKeys);
826 }
827}
828
837std::vector<std::string> ConfigServiceImpl::keys() const {
838 std::vector<std::string> allKeys;
839 getKeysRecursive("", allKeys);
840 return allKeys;
841}
842
851void ConfigServiceImpl::remove(const std::string &rootName) {
852 m_pConf->remove(rootName);
853 m_changed_keys.insert(rootName);
854}
855
862bool ConfigServiceImpl::hasProperty(const std::string &rootName) const { return m_pConf->hasProperty(rootName); }
863
864namespace {
867std::string expandEnvironmentInFilepath(const std::string &target) {
868 std::string result = target;
869 size_t pos = 0;
870
871#ifdef _WIN32
872 // Windows style: %VAR%
873 while ((pos = result.find('%', pos)) != std::string::npos) {
874 size_t end = result.find('%', pos + 1);
875 if (end == std::string::npos)
876 break;
877
878 std::string varName = result.substr(pos + 1, end - pos - 1);
879 const char *envValue = std::getenv(varName.c_str());
880
881 if (envValue) {
882 result.replace(pos, end - pos + 1, envValue);
883 pos += std::strlen(envValue);
884 } else {
885 pos = end + 1;
886 }
887 }
888#else
889 // Unix style: $VAR or ${VAR}
890 pos = 0;
891 while ((pos = result.find('$', pos)) != std::string::npos) {
892 size_t start = pos;
893 size_t end;
894 std::string varName;
895
896 if (pos + 1 < result.length() && result[pos + 1] == '{') {
897 // ${VAR} format
898 end = result.find('}', pos + 2);
899 if (end == std::string::npos) {
900 pos++;
901 continue;
902 }
903 varName = result.substr(pos + 2, end - pos - 2);
904 end++; // include the closing brace
905 } else {
906 // $VAR format - find end of variable name
907 end = pos + 1;
908 while (end < result.length() && (std::isalnum(result[end]) || result[end] == '_')) {
909 end++;
910 }
911 varName = result.substr(pos + 1, end - pos - 1);
912 }
913
914 if (!varName.empty()) {
915 const char *envValue = std::getenv(varName.c_str());
916 if (envValue) {
917 result.replace(start, end - start, envValue);
918 pos = start + std::strlen(envValue);
919 } else {
920 pos = end;
921 }
922 } else {
923 pos++;
924 }
925 }
926#endif
927
928 return result;
929}
930} // namespace
931
940bool ConfigServiceImpl::isExecutable(const std::string &target) const {
941 try {
942 std::filesystem::path filepath(expandEnvironmentInFilepath(target));
943
944 if (std::filesystem::exists(filepath) && std::filesystem::is_regular_file(filepath)) {
945 // On Unix-like systems, check execute permission
946 // std::filesystem doesn't have a direct equivalent to canExecute
947 // so we use std::filesystem::status
948#ifdef _WIN32
949 // On Windows, check if it's a regular file with .exe, .bat, .cmd extension
950 std::string ext = filepath.extension().string();
951 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
952 return (ext == ".exe" || ext == ".bat" || ext == ".cmd" || ext == ".com");
953#else
954 // On Unix-like systems, check execute permissions
955 auto perms = std::filesystem::status(filepath).permissions();
956 return (perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none ||
957 (perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none ||
958 (perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none;
959#endif
960 } else
961 return false;
962 } catch (const std::exception &) {
963 return false;
964 }
965}
966
979void ConfigServiceImpl::launchProcess(const std::string &programFilePath,
980 const std::vector<std::string> &programArguments) const {
981 try {
982 std::string expTarget = expandEnvironmentInFilepath(programFilePath);
983 Poco::Process::launch(expTarget, programArguments);
984 } catch (Poco::SystemException &e) {
985 throw std::runtime_error(e.what());
986 }
987}
988
994void ConfigServiceImpl::setString(const std::string &key, const std::string &value) {
995 // If the value is unchanged (after any path conversions), there's nothing to
996 // do.
997 const std::string old = getString(key);
998 if (value == old)
999 return;
1000
1001 // Update the internal value
1002 m_pConf->setString(key, value);
1003
1004 // Cache things that are accessed frequently
1005 if (key == "datasearch.directories") {
1007 } else if (key == "instrumentDefinition.directory") {
1009 } else if (key == "defaultsave.directory") {
1011 } else if (key == "logging.channels.consoleChannel.class") {
1012 // this key requires reloading logging for it to take effect
1014 } else if (key == LOG_LEVEL_KEY) {
1015 this->setLogLevel(value);
1016 }
1017
1018 m_notificationCenter.postNotification(new ValueChanged(key, value, old));
1019 m_changed_keys.insert(key);
1020}
1021
1029template <typename T> std::optional<T> ConfigServiceImpl::getValue(const std::string &keyName) {
1030 std::string strValue = getString(keyName);
1031 T output;
1032 int result = Mantid::Kernel::Strings::convert(strValue, output);
1033
1034 if (result != 1) {
1035 return std::nullopt;
1036 }
1037
1038 return std::optional<T>(output);
1039}
1040
1048template <> std::optional<bool> ConfigServiceImpl::getValue(const std::string &keyName) {
1049 auto returnedValue = getValue<std::string>(keyName);
1050 if (!returnedValue.has_value()) {
1051 return std::nullopt;
1052 }
1053
1054 auto &configVal = returnedValue.value();
1055
1056 std::transform(configVal.begin(), configVal.end(), configVal.begin(), ::tolower);
1057
1058 boost::trim(configVal);
1059
1060 bool trueString = configVal == "true";
1061 bool valueOne = configVal == "1";
1062 bool onOffString = configVal == "on";
1063
1064 // A string of 1 or true both count
1065 return trueString || valueOne || onOffString;
1066}
1067
1073#ifdef _WIN32
1074 return "Mantid.local.properties";
1075#else
1076 return "/etc/mantid.local.properties";
1077#endif
1078}
1079
1085
1093std::string ConfigServiceImpl::getEnvironment(const std::string &keyName) {
1094 return m_pSysConfig->getString("system.env." + keyName);
1095}
1096
1101std::string ConfigServiceImpl::getOSName() { return m_pSysConfig->getString("system.osName"); }
1102
1108 auto osArch = m_pSysConfig->getString("system.osArchitecture");
1109#ifdef __APPLE__
1110 if (osArch == "x86_64") {
1111 // This could be running under Rosetta on an Arm Mac
1112 // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment
1113 int ret = 0;
1114 size_t size = sizeof(ret);
1115 if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) != -1 && ret == 1) {
1116 osArch = "arm64_(x86_64)";
1117 g_log.warning("You are running an Intel build of Mantid on Apple silicon, which will be significantly slower and "
1118 "use more power. For best performance, install the Arm version of Mantid. This version is "
1119 "available here: https://downloads.mantidproject.org");
1120 } else {
1121 // TODO this can be removed after the v6.14 code freeze because the feature will be dropped
1122 g_log.warning("Mantid v6.14 is the last version that will support Intel macOS.");
1123 }
1124 }
1125#endif
1126 return osArch;
1127}
1128
1133std::string ConfigServiceImpl::getComputerName() { return m_pSysConfig->getString("system.nodeName"); }
1134
1139std::string ConfigServiceImpl::getOSVersion() { return m_pSysConfig->getString("system.osVersion"); }
1140
1142bool canRead(const std::string &filename) {
1143 // check for existence of the file
1144 if (!std::filesystem::exists(filename)) {
1145 return false;
1146 }
1147
1148 // Try to open the file to check if it's readable
1149 std::ifstream file(filename);
1150 return file.good();
1151}
1152
1154std::string getValueFromStdOut(const std::string &orig, const std::string &key) {
1155 size_t start = orig.find(key);
1156 if (start == std::string::npos) {
1157 return std::string();
1158 }
1159 start += key.size();
1160
1161 size_t stop = orig.find('\n', start);
1162 if (stop == std::string::npos) {
1163 return std::string();
1164 }
1165
1166 return Mantid::Kernel::Strings::strip(orig.substr(start, stop - start));
1167}
1168
1175 std::string description;
1176
1177 // read os-release
1178 static const std::string OS_RELEASE("/etc/os-release");
1179 if (canRead(OS_RELEASE)) {
1180 static const std::string PRETTY_NAME("PRETTY_NAME=");
1181
1182 // open it to see if it has the magic line
1183 std::ifstream handle(OS_RELEASE.c_str(), std::ios::in);
1184
1185 // go through the file
1186 std::string line;
1187 while (std::getline(handle, line)) {
1188 if (line.find(PRETTY_NAME) != std::string::npos) {
1189 if (line.length() > PRETTY_NAME.length() + 1) {
1190 size_t length = line.length() - PRETTY_NAME.length() - 2;
1191 description = line.substr(PRETTY_NAME.length() + 1, length);
1192 }
1193 break;
1194 }
1195 }
1196
1197 // cleanup
1198 handle.close();
1199 if (!description.empty()) {
1200 return description;
1201 }
1202 }
1203
1204 // read redhat-release
1205 static const std::string REDHAT_RELEASE("/etc/redhat-release");
1206 if (canRead(REDHAT_RELEASE)) {
1207 // open it to see if it has the magic line
1208 std::ifstream handle(REDHAT_RELEASE.c_str(), std::ios::in);
1209
1210 // go through the file
1211 std::string line;
1212 while (std::getline(handle, line)) {
1213 if (!line.empty()) {
1214 description = std::move(line);
1215 break;
1216 }
1217 }
1218
1219 // cleanup
1220 handle.close();
1221 if (!description.empty()) {
1222 return description;
1223 }
1224 }
1225
1226 // try system calls
1227 std::string cmd;
1228 std::vector<std::string> args;
1229#ifdef __APPLE__
1230 cmd = "sw_vers"; // mac
1231#elif _WIN32
1232 cmd = "wmic"; // windows
1233 args.emplace_back("os"); // windows
1234 args.emplace_back("get"); // windows
1235 args.emplace_back("Caption"); // windows
1236 args.emplace_back("/value"); // windows
1237#endif
1238
1239#if defined __APPLE__ || defined _WIN32
1240 try {
1241 Poco::Pipe outPipe, errorPipe;
1242 Poco::ProcessHandle ph = Poco::Process::launch(cmd, args, nullptr, &outPipe, &errorPipe);
1243 const int rc = ph.wait();
1244 // Only if the command returned successfully.
1245 if (rc == 0) {
1246 Poco::PipeInputStream pipeStream(outPipe);
1247 std::stringstream stringStream;
1248 Poco::StreamCopier::copyStream(pipeStream, stringStream);
1249 const std::string result = stringStream.str();
1250#ifdef __APPLE__
1251 const std::string product_name = getValueFromStdOut(result, "ProductName:");
1252 const std::string product_vers = getValueFromStdOut(result, "ProductVersion:");
1253
1254 description = product_name + " " + product_vers;
1255#elif _WIN32
1256 description = getValueFromStdOut(result, "Caption=");
1257#else
1258 UNUSED_ARG(result); // only used on mac and windows
1259#endif
1260 } else {
1261 std::stringstream messageStream;
1262 messageStream << "command \"" << cmd << "\" failed with code: " << rc;
1263 g_log.debug(messageStream.str());
1264 }
1265 } catch (Poco::SystemException &e) {
1266 g_log.debug("command \"" + cmd + "\" failed");
1267 g_log.debug(e.what());
1268 }
1269#endif
1270 return description;
1271}
1272
1275 std::string username;
1276
1277 // mac and favorite way to get username on linux
1278 try {
1279 username = m_pSysConfig->getString("system.env.USER");
1280 if (!username.empty()) {
1281 return username;
1282 }
1283 } catch (const Poco::NotFoundException &e) {
1284 UNUSED_ARG(e); // let it drop on the floor
1285 }
1286
1287 // windoze and alternate linux username variable
1288 try {
1289 username = m_pSysConfig->getString("system.env.USERNAME");
1290 if (!username.empty()) {
1291 return username;
1292 }
1293 } catch (const Poco::NotFoundException &e) {
1294 UNUSED_ARG(e); // let it drop on the floor
1295 }
1296
1297 // give up and return an empty string
1298 return std::string();
1299}
1300
1305std::string ConfigServiceImpl::getCurrentDir() { return m_pSysConfig->getString("system.currentDir"); }
1306
1312std::string ConfigServiceImpl::getCurrentDir() const { return m_pSysConfig->getString("system.currentDir"); }
1313
1318std::string ConfigServiceImpl::getTempDir() { return m_pSysConfig->getString("system.tempDir"); }
1319
1325 const std::string applicationName = "mantid";
1326#if POCO_OS == POCO_OS_WINDOWS_NT
1327 const std::string vendorName = "mantidproject";
1328 wchar_t *w_appdata = _wgetenv(L"APPDATA");
1329 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
1330 std::string appdata = converter.to_bytes(w_appdata);
1331 std::filesystem::path path(appdata);
1332 path /= vendorName;
1333 path /= applicationName;
1334 return path.string();
1335#else // linux and mac
1336 const char *home = std::getenv("HOME");
1337 if (!home) {
1338 throw std::runtime_error("HOME environment variable not set - seen in ConfigService.getAppDataDir()");
1339 }
1340 std::filesystem::path path(home);
1341 path /= ("." + applicationName);
1342 return path.string();
1343#endif
1344}
1345
1352 std::filesystem::path execPath(getPathToExecutable());
1353 return execPath.parent_path().string() + "/";
1354}
1355
1362 std::string execpath;
1363 const size_t LEN(1024);
1364 char pBuf[LEN];
1365
1366#ifdef _WIN32
1367 unsigned int bytes = GetModuleFileName(NULL, pBuf, LEN);
1368#elif defined __linux__
1369 char szTmp[32];
1370 sprintf(szTmp, "/proc/%d/exe", getpid());
1371 ssize_t bytes = readlink(szTmp, pBuf, LEN);
1372#elif defined __APPLE__
1373 // Two calls to _NSGetExecutablePath required - first to get size of buffer
1374 uint32_t bytes(0);
1375 _NSGetExecutablePath(pBuf, &bytes);
1376 const int success = _NSGetExecutablePath(pBuf, &bytes);
1377 if (success < 0)
1378 bytes = 1025;
1379#endif
1380
1381 if (bytes > 0 && bytes < 1024) {
1382 pBuf[bytes] = '\0';
1383 execpath = std::string(pBuf);
1384 }
1385 return execpath;
1386}
1387
1393bool ConfigServiceImpl::isNetworkDrive([[maybe_unused]] const std::string &path) {
1394#ifdef _WIN32
1395 // if path is relative get the full one
1396 char buff[MAX_PATH];
1397 GetFullPathName(path.c_str(), MAX_PATH, buff, NULL);
1398 std::string fullName(buff);
1399 size_t i = fullName.find(':');
1400
1401 // if the full path doesn't contain a drive letter assume it's on the network
1402 if (i == std::string::npos)
1403 return true;
1404
1405 fullName.erase(i + 1);
1406 fullName += '\\'; // make sure the name has the trailing backslash
1407 UINT type = GetDriveType(fullName.c_str());
1408 return DRIVE_REMOTE == type;
1409#elif defined __linux__
1410 // This information is only present in the /proc/mounts file on linux. There
1411 // are no drives on
1412 // linux only mount locations therefore the test will have to check the path
1413 // against
1414 // entries in /proc/mounts to see if the filesystem type is NFS or SMB (any
1415 // others ????)
1416 // Each line corresponds to a particular mounted location
1417 // 1st column - device name
1418 // 2nd column - mounted location
1419 // 3rd column - filesystem type commonly ext2, ext3 for hard drives and NFS or
1420 // SMB for
1421 // network locations
1422
1423 std::ifstream mntfile("/proc/mounts");
1424 std::string txtread("");
1425 while (getline(mntfile, txtread)) {
1426 std::istringstream strm(txtread);
1427 std::string devname(""), mntpoint(""), fstype("");
1428 strm >> devname >> mntpoint >> fstype;
1429 if (!strm)
1430 continue;
1431 // I can't be sure that the file system type is always lower case
1432 std::transform(fstype.begin(), fstype.end(), fstype.begin(), toupper);
1433 // Skip the current line if the file system isn't a network one
1434 if (fstype != "NFS" && fstype != "SMB")
1435 continue;
1436 // Now we have a line containing a network filesystem and just need to check
1437 // if the path
1438 // supplied contains the mount location. There is a small complication in
1439 // that the mount
1440 // points within the file have certain characters transformed into their
1441 // octal
1442 // representations, for example spaces->040.
1443 std::string::size_type idx = mntpoint.find("\\0");
1444 if (idx != std::string::npos) {
1445 std::string oct = mntpoint.substr(idx + 1, 3);
1446 strm.str(oct);
1447 int printch(-1);
1448 strm.setf(std::ios::oct, std::ios::basefield);
1449 strm >> printch;
1450 if (printch != -1) {
1451 mntpoint = mntpoint.substr(0, idx) + static_cast<char>(printch) + mntpoint.substr(idx + 4);
1452 }
1453 // Search for this at the start of the path
1454 if (path.find(mntpoint) == 0)
1455 return true;
1456 }
1457 }
1458 return false;
1459#else
1460 // Not yet implemented for the mac
1461 return false;
1462#endif
1463}
1464
1475
1484#ifdef _WIN32
1485 return m_strBaseDir;
1486#else
1487 std::filesystem::path datadir(m_pSysConfig->getString("system.homeDir"));
1488 datadir /= ".mantid";
1489 // Create the directory if it doesn't already exist
1490 std::filesystem::create_directory(datadir);
1491 return datadir.string() + "/";
1492#endif
1493}
1494
1499const std::vector<std::string> &ConfigServiceImpl::getDataSearchDirs() const { return m_dataSearchDirs; }
1500
1505void ConfigServiceImpl::setDataSearchDirs(const std::vector<std::string> &searchDirs) {
1506 std::string searchPaths = boost::join(searchDirs, ";");
1507 setDataSearchDirs(searchPaths);
1508}
1509
1515void ConfigServiceImpl::setDataSearchDirs(const std::string &searchDirs) {
1516 setString("datasearch.directories", searchDirs);
1517}
1518
1524void ConfigServiceImpl::appendDataSearchSubDir(const std::string &subdir) {
1525 if (subdir.empty())
1526 return;
1527
1528 std::filesystem::path subDirPath;
1529 try {
1530 subDirPath = std::filesystem::path(subdir);
1531 } catch (const std::exception &) {
1532 return;
1533 }
1534
1535 // Check if path has trailing slash (indicates directory) and is relative
1536 if (!subDirPath.is_relative()) {
1537 return;
1538 }
1539
1540 auto newDataDirs = m_dataSearchDirs;
1541 for (const auto &path : m_dataSearchDirs) {
1542 std::filesystem::path newDirPath;
1543 try {
1544 newDirPath = std::filesystem::path(path) / subDirPath;
1545 // only add new path if it isn't already there
1546 if (std::find(newDataDirs.begin(), newDataDirs.end(), newDirPath.string()) == newDataDirs.end())
1547 newDataDirs.emplace_back(newDirPath.string());
1548 } catch (const std::exception &) {
1549 continue;
1550 }
1551 }
1552
1553 setDataSearchDirs(newDataDirs);
1554}
1555
1561void ConfigServiceImpl::appendDataSearchDir(const std::string &path) {
1562 if (path.empty())
1563 return;
1564
1565 std::filesystem::path dirPath;
1566 try {
1567 dirPath = std::filesystem::path(path);
1568 // Ensure it has a trailing slash for consistency
1569 std::string pathStr = dirPath.string();
1570 if (pathStr.back() != '/' && pathStr.back() != '\\') {
1571 pathStr += "/";
1572 }
1573 if (!isInDataSearchList(pathStr)) {
1574 auto newSearchString = Strings::join(std::begin(m_dataSearchDirs), std::end(m_dataSearchDirs), ";");
1575 newSearchString.append(";" + path);
1576 setString("datasearch.directories", newSearchString);
1577 }
1578 } catch (const std::exception &) {
1579 return;
1580 }
1581}
1582
1587void ConfigServiceImpl::setInstrumentDirectories(const std::vector<std::string> &directories) {
1588 m_instrumentDirs = directories;
1589}
1590
1595const std::vector<std::string> &ConfigServiceImpl::getInstrumentDirectories() const { return m_instrumentDirs; }
1596
1601const std::string ConfigServiceImpl::getInstrumentDirectory() const { return m_instrumentDirs.back(); }
1607 // Determine the search directory for XML instrument definition files (IDFs)
1608 std::string directoryName = getString("instrumentDefinition.vtp.directory");
1609
1610 if (directoryName.empty()) {
1611 std::filesystem::path path(getAppDataDir());
1612 path /= "instrument";
1613 path /= "geometryCache";
1614 directoryName = path.string();
1615 }
1616 return directoryName;
1617}
1629 m_instrumentDirs.clear();
1630
1631 std::filesystem::path path(getAppDataDir());
1632 path /= "instrument";
1633 const std::string appdatadir = path.string();
1635
1636#ifndef _WIN32
1637 addDirectoryifExists("/etc/mantid/instrument", m_instrumentDirs);
1638#endif
1639
1640 // Determine the search directory for XML instrument definition files (IDFs)
1641 std::string directoryName = getString("instrumentDefinition.directory", true);
1642 if (directoryName.empty()) {
1643 // This is the assumed deployment directory for IDFs, where we need to be
1644 // relative to the
1645 // directory of the executable, not the current working directory.
1646 std::filesystem::path basePath(getPropertiesDir());
1647 directoryName = (basePath / ".." / "instrument").lexically_normal().string();
1648 }
1649 addDirectoryifExists(directoryName, m_instrumentDirs);
1650}
1651
1659bool ConfigServiceImpl::addDirectoryifExists(const std::string &directoryName,
1660 std::vector<std::string> &directoryList) {
1661 try {
1662 if (std::filesystem::is_directory(directoryName)) {
1663 directoryList.emplace_back(directoryName);
1664 return true;
1665 } else {
1666 g_log.information("Unable to locate directory at: " + directoryName);
1667 return false;
1668 }
1669 } catch (const std::filesystem::filesystem_error &) {
1670 g_log.information("Unable to locate directory at: " + directoryName);
1671 return false;
1672 } catch (const std::exception &) {
1673 g_log.information("Unable to locate directory at: " + directoryName);
1674 return false;
1675 }
1676}
1677
1678const std::vector<std::string> ConfigServiceImpl::getFacilityFilenames(const std::string &fName) {
1679 std::vector<std::string> returnPaths;
1680
1681 // first try the supplied file
1682 if (!fName.empty()) {
1683 if (std::filesystem::exists(fName)) {
1684 returnPaths.emplace_back(fName);
1685 return returnPaths;
1686 }
1687 }
1688
1689 // search all of the instrument directories
1690 const auto &directoryNames = getInstrumentDirectories();
1691
1692 // only use downloaded instruments if configured to download
1693 const std::string updateInstrStr = this->getString("UpdateInstrumentDefinitions.OnStartup");
1694
1695 auto instrDir = directoryNames.begin();
1696
1697 // If we are not updating the instrument definitions
1698 // update the iterator, this means we will skip the folder in HOME and
1699 // look in the instrument folder in mantid install directory or mantid source
1700 // code directory
1701 if (!(updateInstrStr == "1" || updateInstrStr == "on" || updateInstrStr == "On") && directoryNames.size() > 1) {
1702 instrDir++;
1703 }
1704
1705 // look through all the possible files
1706 for (; instrDir != directoryNames.end(); ++instrDir) {
1707 std::filesystem::path p(*instrDir);
1708 p /= "Facilities.xml";
1709 std::string filename = p.string();
1710
1711 if (std::filesystem::exists(filename))
1712 returnPaths.emplace_back(filename);
1713 }
1714
1715 if (returnPaths.size() > 0) {
1716 return returnPaths;
1717 }
1718
1719 // getting this far means the file was not found
1720 std::string directoryNamesList = boost::algorithm::join(directoryNames, ", ");
1721 throw std::runtime_error("Failed to find \"Facilities.xml\". Searched in " + directoryNamesList);
1722}
1723
1734void ConfigServiceImpl::updateFacilities(const std::string &fName) {
1736
1737 // Try to find the file. If it does not exist we will crash, and cannot read
1738 // the Facilities file
1739 const auto fileNames = getFacilityFilenames(fName);
1740 size_t attemptIndex = 0;
1741 bool success = false;
1742 while ((!success) && (attemptIndex < fileNames.size())) {
1743 const auto &fileName = fileNames[attemptIndex];
1744 try {
1745 // Set up the DOM parser and parse xml file
1746 Poco::AutoPtr<Poco::XML::Document> pDoc;
1747 try {
1748 Poco::XML::DOMParser pParser;
1749 pDoc = pParser.parse(fileName);
1750 } catch (...) {
1751 throw Kernel::Exception::FileError("Unable to parse file:", fileName);
1752 }
1753
1754 // Get pointer to root element
1755 Poco::XML::Element *pRootElem = pDoc->documentElement();
1756 if (!pRootElem->hasChildNodes()) {
1757 throw std::runtime_error("No root element in Facilities.xml file");
1758 }
1759
1760 const Poco::AutoPtr<Poco::XML::NodeList> pNL_facility = pRootElem->getElementsByTagName("facility");
1761 const size_t n = pNL_facility->length();
1762
1763 for (unsigned long i = 0; i < n; ++i) {
1764 const auto *elem = dynamic_cast<Poco::XML::Element *>(pNL_facility->item(i));
1765 if (elem) {
1766 m_facilities.emplace_back(new FacilityInfo(elem));
1767 }
1768 }
1769
1770 if (m_facilities.empty()) {
1771 throw std::runtime_error("The facility definition file " + fileName + " defines no facilities");
1772 }
1773
1774 // if we got here we have suceeded and can exit the loop
1775 success = true;
1776 } catch (std::runtime_error &ex) {
1777 // log this failure to load a file
1778 g_log.error() << "Failed to load the facilities.xml file at " << fileName << "\nIt might be corrupt. "
1779 << ex.what() << "\nWill try to load another version.\n";
1780 attemptIndex++;
1781 // move on to the next file index if available
1782 if (attemptIndex == fileNames.size()) {
1783 const std::string errorMessage = "No more Facilities.xml files can be found, Mantid will not be "
1784 "able to start, Sorry. Try reinstalling Mantid.";
1785 // This is one of the few times that both logging a messge and throwing
1786 // might make sense
1787 // as the error reporter tends to swallow the thrown message.
1788 g_log.error() << errorMessage << "\n";
1789 // Throw an exception as we have run out of files to try
1790 throw std::runtime_error(errorMessage);
1791 }
1792 }
1793 }
1794}
1795
1799 for (const auto &facility : m_facilities) {
1800 delete facility;
1801 }
1802 m_facilities.clear();
1805}
1806
1813const InstrumentInfo &ConfigServiceImpl::getInstrument(const std::string &instrumentName) const {
1814
1815 // Let's first search for the instrument in our default facility
1816 std::string defaultFacility = getFacility().name();
1817
1818 if (!defaultFacility.empty()) {
1819 try {
1820 g_log.debug() << "Looking for " << instrumentName << " at " << defaultFacility << ".\n";
1821 return getFacility(defaultFacility).instrument(instrumentName);
1822 } catch (Exception::NotFoundError &) {
1823 // Well the instName doesn't exist for this facility
1824 // Move along, there's nothing to see here...
1825 }
1826 }
1827
1828 // Now let's look through the other facilities
1829 for (auto facility : m_facilities) {
1830 try {
1831 g_log.debug() << "Looking for " << instrumentName << " at " << (*facility).name() << ".\n";
1832 return (*facility).instrument(instrumentName);
1833 } catch (Exception::NotFoundError &) {
1834 // Well the instName doesn't exist for this facility...
1835 // Move along, there's nothing to see here...
1836 }
1837 }
1838
1839 const std::string errMsg = "Failed to find an instrument with this name in any facility: '" + instrumentName + "' -";
1840 g_log.debug("Instrument " + instrumentName + " not found");
1841 throw Exception::NotFoundError(errMsg, instrumentName);
1842}
1843
1844const std::string ConfigServiceImpl::findLongestInstrumentPrefix(const std::string &hint) const {
1846 std::vector<std::string> names;
1847
1848 for (const auto &facility : m_facilities) {
1849 const auto &insts = facility->instruments();
1850 for (const auto &inst : insts) {
1851 names.emplace_back(inst.shortName());
1852 names.emplace_back(inst.name());
1853 }
1854 }
1855
1856 std::sort(names.begin(), names.end());
1857 const auto last = std::unique(names.begin(), names.end());
1858 names.erase(last, names.end());
1859 m_instrumentPrefixesCache = std::move(names);
1861 }
1862
1863 std::string longestPrefix;
1864 // Binary search for the hint in the list of instrument prefixes. Since this is a sorted list the longest prefix will
1865 // be found by searching backwards from the insertion point.
1866 const auto match = std::upper_bound(m_instrumentPrefixesCache.cbegin(), m_instrumentPrefixesCache.cend(), hint);
1867 if (match != m_instrumentPrefixesCache.cbegin()) {
1868 auto it = std::prev(match);
1869 while (true) {
1870 if (hint.starts_with(*it)) {
1871 longestPrefix = *it;
1872 break;
1873 }
1874 if ((*it)[0] != hint[0]) {
1875 break;
1876 }
1877 if (it == m_instrumentPrefixesCache.cbegin()) {
1878 break;
1879 }
1880 it = std::prev(it);
1881 }
1882 }
1883
1884 return longestPrefix;
1885}
1886
1890const std::vector<FacilityInfo *> ConfigServiceImpl::getFacilities() const { return m_facilities; }
1891
1895const std::vector<std::string> ConfigServiceImpl::getFacilityNames() const {
1896 auto names = std::vector<std::string>(m_facilities.size());
1897 std::transform(m_facilities.cbegin(), m_facilities.cend(), names.begin(),
1898 [](const FacilityInfo *facility) { return facility->name(); });
1899
1900 return names;
1901}
1902
1907 std::string defFacility = getString("default.facility");
1908 if (defFacility.empty()) {
1909 defFacility = " ";
1910 }
1911 return this->getFacility(defFacility);
1912}
1913
1920const FacilityInfo &ConfigServiceImpl::getFacility(const std::string &facilityName) const {
1921 if (facilityName.empty())
1922 return this->getFacility();
1923
1924 auto facility = std::find_if(m_facilities.cbegin(), m_facilities.cend(),
1925 [&facilityName](const auto f) { return f->name() == facilityName; });
1926
1927 if (facility != m_facilities.cend()) {
1928 return **facility;
1929 }
1930
1931 throw Exception::NotFoundError("Facilities", facilityName);
1932}
1933
1939void ConfigServiceImpl::setFacility(const std::string &facilityName) {
1940 const FacilityInfo *foundFacility = nullptr;
1941
1942 try {
1943 // Get facility looks up by string - so re-use that to check if the facility
1944 // is known
1945 foundFacility = &getFacility(facilityName);
1946 } catch (const Exception::NotFoundError &) {
1947 g_log.error("Failed to set default facility to be " + facilityName + ". Facility not found");
1948 throw;
1949 }
1950 assert(foundFacility);
1951 setString("default.facility", facilityName);
1952
1953 const auto &associatedInsts = foundFacility->instruments();
1954 if (associatedInsts.empty()) {
1955 throw std::invalid_argument("The selected facility has no instruments associated with it");
1956 }
1957
1958 // Update the default instrument to be one from this facility
1959 setString("default.instrument", associatedInsts[0].name());
1960}
1961
1965void ConfigServiceImpl::addObserver(const Poco::AbstractObserver &observer) const {
1966 m_notificationCenter.addObserver(observer);
1967}
1968
1972void ConfigServiceImpl::removeObserver(const Poco::AbstractObserver &observer) const {
1973 m_notificationCenter.removeObserver(observer);
1974}
1975
1976/*
1977Gets the system proxy information
1978@url A url to match the proxy to
1979@return the proxy information.
1980*/
1982 if (!m_isProxySet) {
1983 // set the proxy
1984 // first check if the proxy is defined in the properties file
1985 auto proxyHost = getValue<std::string>("proxy.host");
1986 auto proxyPort = getValue<int>("proxy.port");
1987
1988 if (proxyHost.has_value() && proxyPort.has_value()) {
1989 // set it from the config values
1990 m_proxyInfo = ProxyInfo(proxyHost.value(), proxyPort.value(), true);
1991 } else {
1992 // get the system proxy
1993 Poco::URI uri(url);
1994 Mantid::Kernel::NetworkProxy proxyHelper;
1995 m_proxyInfo = proxyHelper.getHttpProxy(uri.toString());
1996 }
1997 m_isProxySet = true;
1998 }
1999 return m_proxyInfo;
2000}
2001
2002std::string ConfigServiceImpl::getFullPath(const std::string &filename, const bool ignoreDirs,
2003 const int options) const {
2004 std::string fName = Kernel::Strings::strip(filename);
2005 g_log.debug() << "getFullPath(" << fName << ")\n";
2006 // If this is already a full path, nothing to do
2007 std::filesystem::path filepath(fName);
2008 if (filepath.is_absolute())
2009 return fName;
2010 // First try the path relative to the current directory. Can throw in some
2011 // circumstances with extensions that have wild cards
2012 try {
2013 std::filesystem::path fullPath = std::filesystem::current_path() / fName;
2014 if (std::filesystem::exists(fullPath) && (!ignoreDirs || !std::filesystem::is_directory(fullPath)))
2015 return fullPath.string();
2016 } catch (const std::exception &) {
2017 }
2018
2019 auto directoryNames = getDataSearchDirs();
2020 const auto &instrDirectories = getInstrumentDirectories();
2021 directoryNames.insert(directoryNames.end(), instrDirectories.begin(), instrDirectories.end());
2022 for (const auto &searchPath : directoryNames) {
2023 g_log.debug() << "Searching for " << fName << " in " << searchPath << "\n";
2024// On windows globbing is not working properly with network drives
2025// for example a network drive containing a $
2026// For this reason, and since windows is case insensitive anyway
2027// a special case is made for windows
2028#ifdef _WIN32
2029 if (fName.find("*") != std::string::npos) {
2030#endif
2031 std::filesystem::path path = std::filesystem::path(searchPath) / fName;
2032 std::set<std::string> files;
2033 Kernel::Glob::glob(path.string(), files, options);
2034 if (!files.empty()) {
2035 std::filesystem::path matchPath(*files.begin());
2036 if (ignoreDirs && std::filesystem::is_directory(matchPath)) {
2037 continue;
2038 }
2039 return *files.begin();
2040 }
2041#ifdef _WIN32
2042 } else {
2043 std::filesystem::path path = std::filesystem::path(searchPath) / fName;
2044 if (std::filesystem::exists(path) && !(ignoreDirs && std::filesystem::is_directory(path))) {
2045 return path.string();
2046 }
2047 }
2048#endif
2049 }
2050 return "";
2051}
2052
2058void ConfigServiceImpl::setLogLevel(int logLevel, bool quiet) {
2060 // update the internal value to keep strings in sync
2061 m_pConf->setString(LOG_LEVEL_KEY, g_log.getLevelName());
2062
2063 if (!quiet) {
2064 g_log.log("logging set to " + Logger::PriorityNames[std::size_t(logLevel)] + " priority",
2065 static_cast<Logger::Priority>(logLevel));
2066 }
2067}
2068
2069void ConfigServiceImpl::setLogLevel(std::string const &logLevel, bool quiet) {
2071 // update the internal value to keep strings in sync
2072 m_pConf->setString(LOG_LEVEL_KEY, g_log.getLevelName());
2073
2074 if (!quiet) {
2075 g_log.log("logging set to " + logLevel + " priority", static_cast<Logger::Priority>(g_log.getLevel()));
2076 }
2077}
2078
2080
2082template DLLExport std::optional<double> ConfigServiceImpl::getValue(const std::string &);
2083template DLLExport std::optional<std::string> ConfigServiceImpl::getValue(const std::string &);
2084template DLLExport std::optional<int> ConfigServiceImpl::getValue(const std::string &);
2085template DLLExport std::optional<size_t> ConfigServiceImpl::getValue(const std::string &);
2086#ifdef _MSC_VER
2087template DLLExport std::optional<bool> ConfigServiceImpl::getValue(const std::string &);
2088#endif
2089
2091
2092} // namespace Kernel
2093} // 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:33
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:44
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 findLongestInstrumentPrefix(const std::string &hint) const
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::vector< std::string > m_instrumentPrefixesCache
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:85
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.