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