Skip to content
Snippets Groups Projects
ConfigService.cpp 65.1 KiB
Newer Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/MantidVersion.h"
#include "MantidKernel/ParaViewVersion.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/FilterChannel.h"
#include "MantidKernel/StdoutChannel.h"
#include "MantidKernel/Exception.h"
#include "MantidKernel/FacilityInfo.h"
#include "MantidKernel/NetworkProxy.h"
Campbell, Stuart's avatar
Campbell, Stuart committed
#include <Poco/Util/LoggingConfigurator.h>
#include <Poco/Util/SystemConfiguration.h>
#include <Poco/Util/PropertyFileConfiguration.h>
#include <Poco/LoggingFactory.h>
#include <Poco/Path.h>
#include <Poco/File.h>
#include <Poco/StringTokenizer.h>
#include <Poco/DOM/DOMParser.h>
#include <Poco/DOM/Document.h>
#include <Poco/DOM/NodeList.h>
#include <Poco/Environment.h>
#include <Poco/URI.h>
#pragma warning(disable : 4250)
#include <Poco/PipeStream.h>
#include <Poco/StreamCopier.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/regex.hpp>
#include <fstream>
#include <iostream>

Russell Taylor's avatar
Russell Taylor committed
#ifdef __APPLE__
#include <mach-o/dyld.h>
Russell Taylor's avatar
Russell Taylor committed
#endif

namespace Mantid {
/**
 * Get the welcome message for Mantid.
 * @returns A string containing the welcome message for Mantid.
 */
std::string welcomeMessage() {
  return "Welcome to Mantid " +
         std::string(Mantid::Kernel::MantidVersion::version()) +
         "\nPlease cite: " + Mantid::Kernel::MantidVersion::paperCitation() +
         " and this release: " + Mantid::Kernel::MantidVersion::doi();
namespace Kernel {
namespace { // anonymous namespace for some utility functions

/// static Logger object
Logger g_log("ConfigService");

/**
 * Split the supplied string on semicolons.
 *
 * @param path The path to split.
 * @param splitted vector to put the splitted path into.
 */
void splitPath(const std::string &path, std::vector<std::string> &splitted) {
  if (path.find(";") == std::string::npos) { // don't bother tokenizing
    splitted.push_back(path);
    return;
  }

  int options =
      Poco::StringTokenizer::TOK_TRIM + Poco::StringTokenizer::TOK_IGNORE_EMPTY;

  splitted.clear();
  Poco::StringTokenizer tokenizer(path, ";,", options);
  Poco::StringTokenizer::Iterator iend = tokenizer.end();
  splitted.reserve(tokenizer.count());
  for (Poco::StringTokenizer::Iterator itr = tokenizer.begin(); itr != iend;
       ++itr) {
    if (!itr->empty()) {
      splitted.push_back(*itr);
    }
  }
}

} // end of anonymous namespace

/** Inner templated class to wrap the poco library objects that have protected
 *  destructors and expose them as public.
 */
template <typename T> class ConfigServiceImpl::WrappedObject : public T {
public:
  /// The template type of class that is being wrapped
  typedef T element_type;
  /// Simple constructor
  WrappedObject() : T() { m_pPtr = static_cast<T *>(this); }
  template <typename Field> WrappedObject(Field &F) : T(F) {
    m_pPtr = static_cast<T *>(this);
  WrappedObject(const WrappedObject<T> &A) : T(A) {
    m_pPtr = static_cast<T *>(this);
  virtual ~WrappedObject() {}
  /// Overloaded * operator returns the wrapped object pointer
  const T &operator*() const { return *m_pPtr; }
  /// Overloaded * operator returns the wrapped object pointer
  T &operator*() { return m_pPtr; }
  /// Overloaded -> operator returns the wrapped object pointer
  const T *operator->() const { return m_pPtr; }
  /// Overloaded -> operator returns the wrapped object pointer
  T *operator->() { return m_pPtr; }
private:
  /// Private pointer to the wrapped class
  T *m_pPtr;
// Back to the ConfigService class itself...
//-------------------------------
// Private member functions
//-------------------------------
/// Private constructor for singleton class
ConfigServiceImpl::ConfigServiceImpl()
    : m_pConf(NULL), m_pSysConfig(NULL), m_changed_keys(), m_ConfigPaths(),
      m_AbsolutePaths(), m_strBaseDir(""), m_PropertyString(""),
      m_properties_file_name("Mantid.properties"),
      // Use a different user properties file for an mpi-enabled build to avoid
      // confusion if both are used on the same filesystem
      m_user_properties_file_name("Mantid-mpi.user.properties"),
      m_user_properties_file_name("Mantid.user.properties"),
      m_DataSearchDirs(), m_UserSearchDirs(), m_InstrumentDirs(),
      m_instr_prefixes(), m_removedFlag("@@REMOVED@@"), m_proxyInfo(),
      m_isProxySet(false) {
  // getting at system details
  m_pSysConfig = new WrappedObject<Poco::Util::SystemConfiguration>;
  // Register the FilterChannel with the Poco logging factory
  Poco::LoggingFactory::defaultFactory().registerChannelClass(
      "FilterChannel",
      new Poco::Instantiator<Poco::FilterChannel, Poco::Channel>);
  // Register StdChannel with Poco
  Poco::LoggingFactory::defaultFactory().registerChannelClass(
      "StdoutChannel",
      new Poco::Instantiator<Poco::StdoutChannel, Poco::Channel>);
  // Define the directory to search for the Mantid.properties file.
  Poco::File f;

  // First directory: the current working
  m_strBaseDir = Poco::Path::current();
  f = Poco::File(m_strBaseDir + m_properties_file_name);
  if (!f.exists()) {
    // Check the executable directory to see if it includes a mantid.properties
    // file
    f = Poco::File(m_strBaseDir + m_properties_file_name);
    if (!f.exists()) {
      // Last, use the MANTIDPATH environment var
      if (Poco::Environment::has("MANTIDPATH")) {
        // Here we have to follow the convention of the rest of this code and
        // add a trailing slash.
        // Note: adding it to the MANTIDPATH itself will make other parts of the
        // code crash.
        m_strBaseDir = Poco::Environment::get("MANTIDPATH") + "/";
      }
Nick Draper's avatar
Nick Draper committed
    }
  // Assert that the appdata and the instrument subdirectory exists
  std::string appDataDir = getAppDataDir();
  Poco::Path path(appDataDir);
  path.pushDirectory("instrument");
  Poco::File file(path);
  // createdirectories will fail gracefully if it is already present
  // Fill the list of possible relative path keys that may require conversion to
  // absolute paths
  m_ConfigPaths.insert(
      std::make_pair("mantidqt.python_interfaces_directory", true));
  m_ConfigPaths.insert(std::make_pair("plugins.directory", true));
  m_ConfigPaths.insert(std::make_pair("pvplugins.directory", true));
  m_ConfigPaths.insert(std::make_pair("mantidqt.plugins.directory", true));
  m_ConfigPaths.insert(std::make_pair("instrumentDefinition.directory", true));
  m_ConfigPaths.insert(std::make_pair("groupingFiles.directory", true));
  m_ConfigPaths.insert(std::make_pair("maskFiles.directory", true));
  m_ConfigPaths.insert(std::make_pair("colormaps.directory", true));
  m_ConfigPaths.insert(
      std::make_pair("requiredpythonscript.directories", true));
  m_ConfigPaths.insert(std::make_pair("pythonscripts.directory", true));
  m_ConfigPaths.insert(std::make_pair("pythonscripts.directories", true));
  m_ConfigPaths.insert(std::make_pair("python.plugins.directories", true));
  m_ConfigPaths.insert(std::make_pair("user.python.plugins.directories", true));
  m_ConfigPaths.insert(std::make_pair("datasearch.directories", true));
  m_ConfigPaths.insert(std::make_pair("icatDownload.directory", true));

  // attempt to load the default properties file that resides in the directory
  // of the executable
  updateConfig(getPropertiesDir() + m_properties_file_name, false, false);
  propertiesFilesList = getPropertiesDir() + m_properties_file_name;

  // Load the local (machine) properties file, if it exists
  Poco::File localFile(getLocalFilename());
  if (localFile.exists()) {
    updateConfig(getLocalFilename(), true, false);
    propertiesFilesList += ", " + getLocalFilename();
  }
  if (Poco::Environment::has("MANTIDPROPERTIES")) {
    // and then append the user properties
    updateConfig(getUserFilename(), true, false);
    propertiesFilesList += ", " + getUserFilename();
    // and the extra one from the environment
    updateConfig(Poco::Environment::get("MANTIDPROPERTIES"), true, true);
    propertiesFilesList += ", " + Poco::Environment::get("MANTIDPROPERTIES");
  } else {
    // Just do the user properties
    updateConfig(getUserFilename(), true, true);
    propertiesFilesList += ", " + getUserFilename();
  }

  updateFacilities();

  g_log.debug() << "ConfigService created." << std::endl;
  g_log.debug() << "Configured Mantid.properties directory of application as "
                << getPropertiesDir() << std::endl;
  g_log.information() << "This is Mantid version " << MantidVersion::version()
                      << " revision " << MantidVersion::revision() << std::endl;
  g_log.information() << "Properties file(s) loaded: " << propertiesFilesList
                      << std::endl;
#ifndef MPI_BUILD // There is no logging to file by default in MPI build
  g_log.information() << "Logging to: " << m_logFilePath << std::endl;
}

/** Private Destructor
 *  Prevents client from calling 'delete' on the pointer handed out by Instance
 */
ConfigServiceImpl::~ConfigServiceImpl() {
  // std::cerr << "ConfigService destroyed." << std::endl;
  Kernel::Logger::shutdown();
  delete m_pSysConfig;
  delete m_pConf; // potential double delete???
 *  If the file contains logging setup instructions then these will be used to
 *setup the logging framework.
 *  @param filename :: The filename and optionally path of the file to load
 *  @param append :: If false (default) then any previous configuration is
 *discarded, otherwise the new keys are added, and repeated keys will override
 *existing ones.
void ConfigServiceImpl::loadConfig(const std::string &filename,
                                   const bool append) {
  if (!append) {
    // remove the previous property string
  try {
    // slurp in entire file
    std::string temp;
    bool good = readFile(filename, temp);
    // check if we have failed to open the file
    if ((!good) || (temp == "")) {
      if (filename == getUserPropertiesDir() + m_user_properties_file_name) {
        // write out a fresh file
      } else {
        throw Exception::FileError("Cannot open file", filename);
    // store the property string
    if ((append) && (m_PropertyString != "")) {
      m_PropertyString = m_PropertyString + "\n" + temp;
    } else {
  }
  catch (std::exception &e) {
    // there was a problem loading the file - it probably is not there
    std::cerr << "Problem loading the configuration file " << filename << " "
              << e.what() << std::endl;
    if (!append) {
      // if we have no property values then take the default
      m_PropertyString = defaultConfig();
  // use the cached property string to initialise the POCO property file
  std::istringstream istr(m_PropertyString);
  m_pConf = new WrappedObject<Poco::Util::PropertyFileConfiguration>(istr);
}

/**
 * Read a file and place its contents into the given string
 * @param filename :: The filename of the file to read
 * @param contents :: The file contents will be placed here
 * @returns A boolean indicating whether opening the file was successful
 */
bool ConfigServiceImpl::readFile(const std::string &filename,
                                 std::string &contents) const {
  std::ifstream propFile(filename.c_str(), std::ios::in);
  bool good = propFile.good();
  if (!good) {
    propFile.close();
    return good;
  }
  // slurp in entire file - extremely unlikely delimiter used as an alternate to
  // \n
  contents.clear();
  getline(propFile, contents, '`');
  propFile.close();
  return good;
}

/** Configures the Poco logging and starts it up
 *
 */
void ConfigServiceImpl::configureLogging() {
  try {
    // Ensure that the logging directory exists
    m_logFilePath = getString("logging.channels.fileChannel.path");
    Poco::Path logpath(m_logFilePath);

Janik Zikovsky's avatar
Janik Zikovsky committed
    // Undocumented way to override the mantid.log path
    if (Poco::Environment::has("MANTIDLOGPATH")) {
Janik Zikovsky's avatar
Janik Zikovsky committed
      logpath = Poco::Path(Poco::Environment::get("MANTIDLOGPATH"));
      logpath = logpath.absolute();
      m_logFilePath = logpath.toString();
    }
Janik Zikovsky's avatar
Janik Zikovsky committed

    // An absolute path makes things simpler
    logpath = logpath.absolute();

    if (!m_logFilePath.empty()) {
      try {
        // Save it for later
        m_logFilePath = logpath.toString();

        // make this path point to the parent directory and create it if it does
        // not exist
        Poco::Path parent = logpath;
        parent.makeParent();
        Poco::File(parent).createDirectories();

        // Try to create or append to the file. If it fails, use the default
        FILE *fp = fopen(m_logFilePath.c_str(), "a+");
        if (fp == NULL) {
          std::cerr
              << "Error writing to log file path given in properties file: \""
              << m_logFilePath << "\". Will use a default path instead."
              << std::endl;
          // Clear the path; this will make it use the default
          m_logFilePath = "";
      }
      catch (std::exception &) {
        std::cerr
            << "Error writing to log file path given in properties file: \""
            << m_logFilePath << "\". Will use a default path instead."
            << std::endl;
        // ERROR! Maybe the file is not writable!
        // Clear the path; this will make it use the default
        m_logFilePath = "";
      }
    }

    // The path given was invalid somehow? Use a default
    if (m_logFilePath.empty()) {
      m_logFilePath = getUserPropertiesDir() + "mantid.log";
      // Check whether the file can be written. The Poco::File::canWrite method
      // does not work
      // for files that don't exist, it throws an exception. It also can't be
      // used to check for
      // directory access as the Windows API doesn't return this information
      // correctly for
      // directories.
      FILE *fp = fopen(m_logFilePath.c_str(), "a+");
      if (!fp) {
        // if we cannot write to the default directory then set use the system
        // temp
        logpath = Poco::Path::temp() + "mantid.log";
        m_logFilePath = logpath.toString();
        std::cerr << "Error writing to log file path to default location: \""
                  << m_logFilePath
                  << "\". Will use a system temp path instead: \""
                  << m_logFilePath << "\"" << std::endl;
      } else
        fclose(fp);
    // Set the line in the configuration properties.
    //  this'll be picked up by LoggingConfigurator (somehow)
    m_pConf->setString("logging.channels.fileChannel.path", m_logFilePath);

    // make this path point to the parent directory and create it if it does not
    // exist
    if (!logpath.toString().empty()) {
      Poco::File(logpath)
          .createDirectories(); // Also creates all necessary directories
    Poco::Util::LoggingConfigurator configurator;
    configurator.configure(m_pConf);
  }
  catch (std::exception &e) {
    std::cerr << "Trouble configuring the logging framework " << e.what()
              << std::endl;
  }
 * Searches the stored list for keys that have been loaded from the config file
 * and may contain
 * relative paths. Any it find are converted to absolute paths and stored
 * separately
void ConfigServiceImpl::convertRelativeToAbsolute() {
  m_AbsolutePaths.clear();
  std::map<std::string, bool>::const_iterator send = m_ConfigPaths.end();
  for (std::map<std::string, bool>::const_iterator sitr = m_ConfigPaths.begin();
       sitr != send; ++sitr) {
    std::string key = sitr->first;
    if (!m_pConf->hasProperty(key))
      continue;
    std::string value(m_pConf->getString(key));
    value = makeAbsolute(value, key);
    m_AbsolutePaths.insert(std::make_pair(key, value));
  }
}

/**
 * Make a relative path or a list of relative paths into an absolute one.
 * @param dir :: The directory to convert
 * @param key :: The key variable this relates to
 * @returns A string containing an absolute path by resolving the relative
 * directory with the executable directory
std::string ConfigServiceImpl::makeAbsolute(const std::string &dir,
                                            const std::string &key) const {
  if (dir.empty()) {
    // Don't do anything for an empty value
    return dir;
  }
  std::string converted;
  // If we have a list, chop it up and convert each one
  if (dir.find_first_of(";,") != std::string::npos) {
    std::vector<std::string> splitted;
    splitPath(dir, splitted);
    std::vector<std::string>::const_iterator iend = splitted.end();
    for (std::vector<std::string>::const_iterator itr = splitted.begin();
         itr != iend;) {
      std::string absolute = makeAbsolute(*itr, key);
      if (absolute.empty()) {
      } else {
        if (++itr != iend) {
        }
      }
    }
    return converted;
  // MG 05/10/09: When the Poco::FilePropertyConfiguration object reads its
  // key/value pairs it
  // treats a backslash as the start of an escape sequence. If the next
  // character does not
  // form a valid sequence then the backslash is removed from the stream. This
  // has the effect
  // of giving malformed paths when using Windows-style directories. E.g
  // C:\Mantid ->C:Mantid
  // and Poco::Path::isRelative throws an exception on this
  bool is_relative(false);
    is_relative = Poco::Path(dir).isRelative();
  }
  catch (Poco::PathSyntaxException &) {
    g_log.warning() << "Malformed path detected in the \"" << key
                    << "\" variable, skipping \"" << dir << "\"\n";
  if (is_relative) {
    const std::string propFileDir(getPropertiesDir());
    converted = Poco::Path(propFileDir).resolve(dir).toString();
  } else {
  converted = Poco::Path(converted).makeDirectory().toString();
  // C++ doesn't have a const version of operator[] for maps so I can't call
  // that here
  std::map<std::string, bool>::const_iterator it = m_ConfigPaths.find(key);
  bool required = false;
  if (it != m_ConfigPaths.end()) {
  try {
    if (required && !Poco::File(converted).exists()) {
      g_log.debug() << "Required properties path \"" << converted
                    << "\" in the \"" << key << "\" variable does not exist.\n";
  }
  catch (Poco::FileException &) {
    g_log.debug() << "Required properties path \"" << converted
                  << "\" in the \"" << key << "\" variable does not exist.\n";
  // Backward slashes cannot be allowed to go into our properties file
  // Note this is a temporary fix for ticket #2445.
  // Ticket #2460 prompts a review of our path handling in the config service.
  boost::replace_all(converted, "\\", "/");
 * Create the store of data search paths from the 'datasearch.directories' key
 * within the Mantid.properties file.
 * The value of the key should be a semi-colon separated list of directories
 */
void ConfigServiceImpl::cacheDataSearchPaths() {
  m_DataSearchDirs.clear();
  std::string paths = getString("datasearch.directories");
  // Nothing to do
  splitPath(paths, m_DataSearchDirs);
 * Create the store of user search paths from the 'usersearch.directories' key
 * within the Mantid.properties file.
 * The value of the key should be a semi-colon separated list of directories
 */
void ConfigServiceImpl::cacheUserSearchPaths() {
  m_UserSearchDirs.clear();
  std::string paths = getString("usersearch.directories");
  // Nothing to do
  splitPath(paths, m_UserSearchDirs);
/**
 *  The path that is passed should be as returned by makeAbsolute() and
 *  this function will return true if that path is in the list
 *  @param path :: the absolute path name to search for
bool ConfigServiceImpl::isInDataSearchList(const std::string &path) const {
  // the path produced by poco will have \ on windows, but the searchdirs will
  // always have /
  std::string correctedPath = path;
  replace(correctedPath.begin(), correctedPath.end(), '\\', '/');

  std::vector<std::string>::const_iterator it =
      std::find_if(m_DataSearchDirs.begin(), m_DataSearchDirs.end(),
                   std::bind2nd(std::equal_to<std::string>(), correctedPath));
/**
 * writes a basic placeholder user.properties file to disk
 * any errors are caught and logged, but not propagated
 */
void ConfigServiceImpl::createUserPropertiesFile() const {
  try {
    std::fstream filestr(
        (getUserPropertiesDir() + m_user_properties_file_name).c_str(),
    filestr << "# This file can be used to override any properties for this "
               "installation." << std::endl;
    filestr << "# Any properties found in this file will override any that are "
               "found in the Mantid.Properties file" << std::endl;
    filestr << "# As this file will not be replaced with futher installations "
               "of Mantid it is a safe place to put " << std::endl;
    filestr << "# properties that suit your particular installation."
            << std::endl;
    filestr << "#" << std::endl;
    filestr << "# See here for a list of possible options:" << std::endl;
    filestr << "# "
               "http://www.mantidproject.org/"
               "Properties_File#Mantid.User.Properties" << std::endl;
    filestr << "##" << std::endl;
    filestr << "## GENERAL" << std::endl;
    filestr << "##" << std::endl;
    filestr << std::endl;
    filestr << "## Set the number of algorithm properties to retain"
            << std::endl;
    filestr << "#algorithms.retained=90" << std::endl;
    filestr << std::endl;
    filestr << "## Hides catagories from the algorithm list in MantidPlot"
            << std::endl;
    filestr << "#algorithms.catagories.hidden=Muons,Inelastic" << std::endl;
    filestr << std::endl;
    filestr << "## Set the maximum number of coures used to run algorithms over"
            << std::endl;
    filestr << "#MultiThreaded.MaxCores=4" << std::endl;
    filestr << std::endl;
    filestr << "##" << std::endl;
    filestr << "## FACILITY AND INSTRUMENT" << std::endl;
    filestr << "##" << std::endl;
    filestr << std::endl;
    filestr << "## Sets the default facility" << std::endl;
    filestr << "## e.g.: ISIS, SNS, ILL" << std::endl;
    filestr << "default.facility=" << std::endl;
    filestr << std::endl;
    filestr << "## Stes the default instrument" << std::endl;
    filestr << "## e.g. IRIS, HET, NIMROD" << std::endl;
    filestr << "default.instrument=" << std::endl;
    filestr << std::endl;
    filestr << "##" << std::endl;
    filestr << "## DIRECTORIES" << std::endl;
    filestr << "##" << std::endl;
    filestr << std::endl;
    filestr << "## Sets a list of directories (separated by semi colons) to "
               "search for data" << std::endl;
    filestr << "#datasearch.directories=../data;../isis/data" << std::endl;
    filestr << std::endl;
    filestr << "## Set a list (separated by semi colons) of directories to "
               "look for additional Python scripts" << std::endl;
    filestr << "#pythonscripts.directories=../scripts;../docs/MyScripts"
            << std::endl;
    filestr << std::endl;
    filestr << "## Uncomment to enable archive search - ICat and Orbiter"
            << std::endl;
    filestr << "#datasearch.searcharchive=On" << std::endl;
    filestr << std::endl;
    filestr << "## Sets default save directory" << std::endl;
    filestr << "#defaultsave.directory=../data" << std::endl;
    filestr << std::endl;
    filestr << "##" << std::endl;
    filestr << "## LOGGING" << std::endl;
    filestr << "##" << std::endl;
    filestr << std::endl;
    filestr << "## Uncomment to change logging level" << std::endl;
    filestr << "## Default is information" << std::endl;
    filestr << "## Valid values are: error, warning, notice, information, debug"
            << std::endl;
    filestr << "#logging.loggers.root.level=information" << std::endl;
    filestr << std::endl;
    filestr << "## Sets the lowest level messages to be logged to file"
            << std::endl;
    filestr << "## Default is warning" << std::endl;
    filestr << "## Valid values are: error, warning, notice, information, debug"
            << std::endl;
    filestr << "#logging.channels.fileFilterChannel.level=debug" << std::endl;
    filestr << std::endl;
    filestr << "## Sets the file to write logs to" << std::endl;
    filestr << "#logging.channels.fileChannel.path=../mantid.log" << std::endl;
    filestr << std::endl;
    filestr << "##" << std::endl;
    filestr << "## MantidPlot" << std::endl;
    filestr << "##" << std::endl;
    filestr << std::endl;
    filestr << "## Show invisible workspaces" << std::endl;
    filestr << "#MantidOptions.InvisibleWorkspaces=0" << std::endl;
    filestr << "## Re-use plot instances for different plot types" << std::endl;
    filestr << "#MantidOptions.ReusePlotInstances=Off" << std::endl;
    filestr << "## Uncomment to disable use of OpenGL to render unwrapped "
               "instrument views" << std::endl;
    filestr << "#MantidOptions.InstrumentView.UseOpenGL=Off" << std::endl;
  catch (std::runtime_error &ex) {
    g_log.warning() << "Unable to write out user.properties file to "
                    << getUserPropertiesDir() << m_user_properties_file_name
                    << " error: " << ex.what() << std::endl;
 * Provides a default Configuration string to use if the config file cannot be
 * loaded.
 * @returns The string value of default properties
 */
std::string ConfigServiceImpl::defaultConfig() const {
  std::string propFile =
      "# logging configuration"
      "# root level message filter (drop to debug for more messages)"
      "logging.loggers.root.level = debug"
      "# splitting the messages to many logging channels"
      "logging.loggers.root.channel.class = SplitterChannel"
      "logging.loggers.root.channel.channel1 = consoleChannel"
      "logging.loggers.root.channel.channel2 = fileFilterChannel"
      "# output to the console - primarily for console based apps"
      "logging.channels.consoleChannel.class = ConsoleChannel"
      "logging.channels.consoleChannel.formatter = f1"
      "# specfic filter for the file channel raising the level to warning "
      "(drop to debug for debugging)"
      "logging.channels.fileFilterChannel.class= FilterChannel"
      "logging.channels.fileFilterChannel.channel= fileChannel"
      "logging.channels.fileFilterChannel.level= warning"
      "# output to a file (For error capturing and debugging)"
      "logging.channels.fileChannel.class = debug"
      "logging.channels.fileChannel.path = ../logs/mantid.log"
      "logging.channels.fileChannel.formatter.class = PatternFormatter"
      "logging.channels.fileChannel.formatter.pattern = %Y-%m-%d %H:%M:%S,%i "
      "[%I] %p %s - %t"
      "logging.formatters.f1.class = PatternFormatter"
      "logging.formatters.f1.pattern = %s-[%p] %t"
      "logging.formatters.f1.times = UTC";
  return propFile;
}

//-------------------------------
// Public member functions
//-------------------------------

/**
 * Removes the user properties file & loads a fresh configuration
 */
void ConfigServiceImpl::reset() {
  // Remove the current user properties file and write a fresh one
    Poco::File userFile(getUserFilename());
    userFile.remove();
  }
  catch (Poco::Exception &) {
  // Now load the original
  const bool append = false;
  const bool updateCaches = true;
  updateConfig(getPropertiesDir() + m_properties_file_name, append,
               updateCaches);
/** Updates and existing configuration and restarts the logging
 *  @param filename :: The filename and optionally path of the file to load
 *  @param append ::   If false (default) then any previous configuration is
 * discarded,
 *                  otherwise the new keys are added, and repeated keys will
 * override existing ones.
 *  @param update_caches :: If true(default) then the various property caches
 * are updated
void ConfigServiceImpl::updateConfig(const std::string &filename,
                                     const bool append,
                                     const bool update_caches) {
  // Ensure that the default save directory makes sense
Doucet, Mathieu's avatar
Doucet, Mathieu committed
  if (!append)
  {
    std::string save_dir = getString("defaultsave.directory");
    if (Poco::trimInPlace(save_dir).size() == 0)
Doucet, Mathieu's avatar
Doucet, Mathieu committed
      setString("defaultsave.directory", Poco::Path::home());
  if (update_caches) {
    // Only configure logging once
    configureLogging();
    // Ensure that any relative paths given in the configuration file are
    // relative to the correct directory
    // Configure search paths into a specially saved store as they will be used
    // frequently
    appendDataSearchDir(getString("defaultsave.directory"));
 * @param filename :: The filename for the saved configuration
 * @throw std::runtime_error if the file cannot be opened
void ConfigServiceImpl::saveConfig(const std::string &filename) const {
  // Open and read the user properties file
  std::string updated_file("");

  std::ifstream reader(filename.c_str(), std::ios::in);
  if (reader.bad()) {
    throw std::runtime_error("Error opening user properties file. Cannot save "
                             "updated configuration.");
  std::string file_line(""), output("");
  bool line_continuing(false);
  while (std::getline(reader, file_line)) {
    if (!file_line.empty()) {
      if (last == '\\') {
        // If we are not in line continuation mode then need
        // a fresh start line
        line_continuing = true;
        output += file_line + "\n";
      } else if (line_continuing) {
      } else {
    } else {
    } // end if-else
    // Output is the current line in the file

    // Extract the key from the current line
    std::string key;
    std::string::size_type pos = output.find('=');
    if (pos == std::string::npos) {
      key = output; // If no equals then the entire thing is the key
    } else {
      key = output.substr(0, pos); // Strip the equals to get only the key
    // Now deal with trimming (removes spaces)
    // Find the comments
    std::string::size_type comment = key.find('#');
    // Check if it exists in the service using hasProperty and make sure it
    // isn't a comment
    if (comment == 0) {
    } else if (!hasProperty(key)) {
      // Remove the key from the changed key list
      m_changed_keys.erase(key);
      continue;
    } else {
      // If it does exist make sure the value is current
      std::string value = getString(key, false);
      Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
      updated_file += key + "=" + value;
      // Remove the key from the changed key list
    updated_file += "\n";
  // Any remaining keys within the changed key store weren't present in the
  // current user properties so append them
  if (!m_changed_keys.empty()) {
    updated_file += "\n";
    std::set<std::string>::iterator key_end = m_changed_keys.end();
    for (std::set<std::string>::iterator key_itr = m_changed_keys.begin();
         key_itr != key_end;) {
      std::string value = getString(*key_itr, false);
      Poco::replaceInPlace(value, "\\", "\\\\"); // replace single \ with double
      updated_file += value;
      if (++key_itr != key_end) {
  // Write out the new file
  std::ofstream writer(filename.c_str(), std::ios_base::trunc);
  if (writer.bad()) {
    g_log.error() << "Error writing new user properties file. Cannot save "
                     "current configuration.\n";
    throw std::runtime_error("Error writing new user properties file. Cannot "
                             "save current configuration.");
  writer.write(updated_file.c_str(), updated_file.size());
  writer.close();
}

/** Searches for a string within the currently loaded configuaration values and
 *  returns the value as a string. If the key is one of those that was a
 *possible relative path
 *  @param keyName :: The case sensitive name of the property that you need the
 *value of.
 *  @param use_cache :: If true, the local cache of directory names is queried
 *first.
 *  @returns The string value of the property, or an empty string if the key
 *cannot be found
std::string ConfigServiceImpl::getString(const std::string &keyName,
                                         bool use_cache) const {
  if (use_cache) {
    std::map<std::string, std::string>::const_iterator mitr =
        m_AbsolutePaths.find(keyName);
    if (mitr != m_AbsolutePaths.end()) {
    if (retVal == m_removedFlag)
      retVal = "";
  }
  catch (Poco::NotFoundException &) {
    g_log.debug() << "Unable to find " << keyName << " in the properties file"
                  << std::endl;
/** Searches for keys within the currently loaded configuaration values and
 *  returns them as strings in a vector.
 *  @param keyName :: The case sensitive name of the property that you need the
 *key for.
 *  @returns The string value of each key within a vector, or an empty vector if
 *there isn't
std::vector<std::string>
ConfigServiceImpl::getKeys(const std::string &keyName) const {
  std::vector<std::string> rawKeys;
  keyVector.reserve(rawKeys.size());
  try {
    m_pConf->keys(keyName, rawKeys);
    // Work around a limitation of Poco < v1.4 which has no remove functionality
    // so check those that have been marked with the correct flag
    const size_t nraw = rawKeys.size();
    for (size_t i = 0; i < nraw; ++i) {
      const std::string key = rawKeys[i];
      try {
        if (m_pConf->getString(key) == m_removedFlag)
          continue;
      catch (Poco::NotFoundException &) {
  catch (Poco::NotFoundException &) {
    g_log.debug() << "Unable to find " << keyName << " in the properties file"
                  << std::endl;
 * Recursively gets a list of all config options from a given root node.
 *
 * @return Vector containing all config options
 */
Dan Nixon's avatar
Dan Nixon committed
void ConfigServiceImpl::getKeysRecursive(const std::string &root,