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