Newer
Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/MantidVersion.h"
Janik Zikovsky
committed
#include "MantidKernel/Strings.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/FilterChannel.h"
#include "MantidKernel/SignalChannel.h"
#include "MantidKernel/Exception.h"
Roman Tolchenov
committed
#include "MantidKernel/FacilityInfo.h"
#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/Element.h>
#include <Poco/DOM/NodeList.h>
#include <Poco/Notification.h>
#include <Poco/Environment.h>
Robert Whitley
committed
#include <Poco/Process.h>
Robert Whitley
committed
#include <Poco/String.h>
Gigg, Martyn Anthony
committed
#include <boost/algorithm/string/replace.hpp>
Gigg, Martyn Anthony
committed
#include <boost/algorithm/string/join.hpp>
Gigg, Martyn Anthony
committed
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
Janik Zikovsky
committed
/**
* Get the welcome message for Mantid.
* @returns A string containing the welcome message for Mantid.
*/
std::string welcomeMessage()
{
return "Welcome to Mantid - Manipulation and Analysis Toolkit for Instrument Data";
}
Campbell, Stuart
committed
/** 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);
}
/** Constructor with a class to wrap
Janik Zikovsky
committed
* @param F :: The object to wrap
Campbell, Stuart
committed
template<typename Field>
WrappedObject(Field& F) :
T(F)
{
m_pPtr = static_cast<T*> (this);
}
Campbell, Stuart
committed
/// Copy constructor
WrappedObject(const WrappedObject<T>& A) :
T(A)
{
m_pPtr = static_cast<T*> (this);
}
Campbell, Stuart
committed
/// Virtual destructor
virtual ~WrappedObject()
{
}
Campbell, Stuart
committed
/// 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;
}
Campbell, Stuart
committed
private:
/// Private pointer to the wrapped class
T* m_pPtr;
};
Campbell, Stuart
committed
//Back to the ConfigService class itself...
Campbell, Stuart
committed
//-------------------------------
// Private member functions
//-------------------------------
Roman Tolchenov
committed
Campbell, Stuart
committed
/// Private constructor for singleton class
ConfigServiceImpl::ConfigServiceImpl() :
m_pConf(NULL), m_pSysConfig(NULL), g_log(Logger::get("ConfigService")), m_changed_keys(),
m_ConfigPaths(), m_AbsolutePaths(), m_strBaseDir(""), m_PropertyString(""),
m_properties_file_name("Mantid.properties"),
Janik Zikovsky
committed
m_user_properties_file_name("Mantid.user.properties"), m_DataSearchDirs(), m_UserSearchDirs(),
m_instr_prefixes(), m_removedFlag("@@REMOVED@@")
Campbell, Stuart
committed
{
//getting at system details
m_pSysConfig = new WrappedObject<Poco::Util::SystemConfiguration> ;
m_pConf = 0;
//Register the FilterChannel with the Poco logging factory
Poco::LoggingFactory::defaultFactory().registerChannelClass("FilterChannel", new Poco::Instantiator<
Poco::FilterChannel, Poco::Channel>);
//Register the SignalChannel with the Poco logging factory
Poco::LoggingFactory::defaultFactory().registerChannelClass("SignalChannel", new Poco::Instantiator<
Poco::SignalChannel, Poco::Channel>);
Gigg, Martyn Anthony
committed
// Define the directory to search for the Mantid.properties file.
Janik Zikovsky
committed
Poco::File f;
// First directory: the current working
m_strBaseDir = Poco::Path::current();
f = Poco::File(m_strBaseDir + m_properties_file_name);
Janik Zikovsky
committed
// Check the executable directory to see if it includes a mantid.properties file
Gigg, Martyn Anthony
committed
m_strBaseDir = getDirectoryOfExecutable();
Janik Zikovsky
committed
f = Poco::File(m_strBaseDir + m_properties_file_name);
if (!f.exists())
Janik Zikovsky
committed
// 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") + "/";
}
Campbell, Stuart
committed
//Fill the list of possible relative path keys that may require conversion to absolute paths
m_ConfigPaths.insert(std::make_pair("plugins.directory", true));
Russell Taylor
committed
m_ConfigPaths.insert(std::make_pair("mantidqt.plugins.directory", true));
Campbell, Stuart
committed
m_ConfigPaths.insert(std::make_pair("instrumentDefinition.directory", true));
Russell Taylor
committed
m_ConfigPaths.insert(std::make_pair("parameterDefinition.directory", true));
Campbell, Stuart
committed
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("ManagedWorkspace.FilePath", true));
m_ConfigPaths.insert(std::make_pair("datasearch.directories", true));
m_ConfigPaths.insert(std::make_pair("pythonalgorithms.directories", true));
m_ConfigPaths.insert(std::make_pair("icatDownload.directory", true));
Doucet, Mathieu
committed
m_ConfigPaths.insert(std::make_pair("mantidqt.python_interfaces_directory", true));
Campbell, Stuart
committed
//attempt to load the default properties file that resides in the directory of the executable
Janik Zikovsky
committed
std::string propertiesFilesList;
Gigg, Martyn Anthony
committed
updateConfig(getPropertiesDir() + m_properties_file_name, false, false);
Janik Zikovsky
committed
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();
}
Janik Zikovsky
committed
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();
}
Campbell, Stuart
committed
updateFacilities();
g_log.debug() << "ConfigService created." << std::endl;
Janik Zikovsky
committed
g_log.debug() << "Configured Mantid.properties directory of application as " << getPropertiesDir()
<< std::endl;
Russell Taylor
committed
g_log.information() << "This is Mantid Version " << MantidVersion::version() << std::endl;
Janik Zikovsky
committed
g_log.information() << "Properties file(s) loaded: " << propertiesFilesList << std::endl;
g_log.information() << "Logging to: " << m_logFilePath << std::endl;
// Make sure the log path is shown somewhere.
//std::cout << "Logging to: " << m_logFilePath << std::endl;
Campbell, Stuart
committed
}
/** 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???
for (std::vector<FacilityInfo*>::iterator it = m_facilities.begin(); it != m_facilities.end(); ++it)
Campbell, Stuart
committed
delete *it;
}
m_facilities.clear();
}
/** Loads the config file provided.
* If the file contains logging setup instructions then these will be used to setup the logging framework.
*
Janik Zikovsky
committed
* @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.
Campbell, Stuart
committed
*/
void ConfigServiceImpl::loadConfig(const std::string& filename, const bool append)
{
delete m_pConf;
if (!append)
{
//remove the previous property string
m_PropertyString = "";
Campbell, Stuart
committed
try
Campbell, Stuart
committed
//slurp in entire file
std::string temp;
bool good = readFile(filename, temp);
Campbell, Stuart
committed
// check if we have failed to open the file
if ((!good) || (temp == ""))
Gigg, Martyn Anthony
committed
if (filename == getUserPropertiesDir() + m_user_properties_file_name)
Campbell, Stuart
committed
//write out a fresh file
createUserPropertiesFile();
Campbell, Stuart
committed
throw Exception::FileError("Cannot open file", filename);
Campbell, Stuart
committed
}
Campbell, Stuart
committed
//store the property string
if ((append) && (m_PropertyString != ""))
{
m_PropertyString = m_PropertyString + "\n" + temp;
Campbell, Stuart
committed
else
Campbell, Stuart
committed
m_PropertyString = temp;
Campbell, Stuart
committed
} catch (std::exception& e)
Campbell, Stuart
committed
//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)
Campbell, Stuart
committed
// if we have no property values then take the default
m_PropertyString = defaultConfig();
Campbell, Stuart
committed
}
Campbell, Stuart
committed
//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
Janik Zikovsky
committed
* @param filename :: The filename of the file to read
* @param contents :: The file contents will be placed here
Campbell, Stuart
committed
* @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)
{
contents = "";
propFile.close();
return good;
}
Campbell, Stuart
committed
//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
*
*/
Campbell, Stuart
committed
void ConfigServiceImpl::configureLogging()
{
try
Campbell, Stuart
committed
//Ensure that the logging directory exists
m_logFilePath = getString("logging.channels.fileChannel.path");
Poco::Path logpath(m_logFilePath);
// Undocumented way to override the mantid.log path
if (Poco::Environment::has("MANTIDLOGPATH"))
Gigg, Martyn Anthony
committed
{
logpath = Poco::Path(Poco::Environment::get("MANTIDLOGPATH"));
Gigg, Martyn Anthony
committed
logpath = logpath.absolute();
m_logFilePath = logpath.toString();
}
// An absolute path makes things simpler
logpath = logpath.absolute();
Gigg, Martyn Anthony
committed
// First, try the logpath given
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)
{
Janik Zikovsky
committed
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 = "";
}
else
fclose(fp);
Janik Zikovsky
committed
} catch (std::exception &)
{
Janik Zikovsky
committed
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";
logpath.assign(m_logFilePath);
logpath = logpath.absolute();
m_logFilePath = logpath.toString();
// Set the line in the configuration properties.
// this'll be picked up by LoggingConfigurator (somehow)
m_pConf->setString("logging.channels.fileChannel.path", m_logFilePath);
Campbell, Stuart
committed
//make this path point to the parent directory and create it if it does not exist
logpath.makeParent();
if (!logpath.toString().empty())
Poco::File(logpath).createDirectories(); // Also creates all necessary directories
// Configure the logging framework
Campbell, Stuart
committed
Poco::Util::LoggingConfigurator configurator;
configurator.configure(m_pConf);
Janik Zikovsky
committed
} catch (std::exception& e)
Campbell, Stuart
committed
{
std::cerr << "Trouble configuring the logging framework " << e.what() << std::endl;
Campbell, Stuart
committed
}
Campbell, Stuart
committed
/**
* 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()
{
if (m_ConfigPaths.empty())
return;
Campbell, Stuart
committed
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;
Campbell, Stuart
committed
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.
Janik Zikovsky
committed
* @param dir :: The directory to convert
* @param key :: The key variable this relates to
Campbell, Stuart
committed
* @returns A string containing an aboluste path by resolving the relative directory with the executable directory
*/
std::string ConfigServiceImpl::makeAbsolute(const std::string & dir, const std::string & key) const
{
std::string converted;
// If we have a list, chop it up and convert each one
if (dir.find_first_of(";,") != std::string::npos)
Campbell, Stuart
committed
int options = Poco::StringTokenizer::TOK_TRIM + Poco::StringTokenizer::TOK_IGNORE_EMPTY;
Poco::StringTokenizer tokenizer(dir, ";,", options);
Poco::StringTokenizer::Iterator iend = tokenizer.end();
for (Poco::StringTokenizer::Iterator itr = tokenizer.begin(); itr != iend;)
Campbell, Stuart
committed
std::string absolute = makeAbsolute(*itr, key);
if (absolute.empty())
Campbell, Stuart
committed
++itr;
}
else
{
converted += absolute;
if (++itr != iend)
Campbell, Stuart
committed
converted += ";";
Campbell, Stuart
committed
}
Campbell, Stuart
committed
// 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);
try
Campbell, Stuart
committed
is_relative = Poco::Path(dir).isRelative();
} catch (Poco::PathSyntaxException&)
Campbell, Stuart
committed
{
g_log.warning() << "Malformed path detected in the \"" << key << "\" variable, skipping \"" << dir
<< "\"\n";
return "";
}
if (is_relative)
{
Gigg, Martyn Anthony
committed
const std::string propFileDir(getPropertiesDir());
Gigg, Martyn Anthony
committed
converted = Poco::Path(propFileDir).resolve(dir).toString();
Campbell, Stuart
committed
}
else
{
converted = dir;
Campbell, Stuart
committed
converted = Poco::Path(converted).makeDirectory().toString();
Campbell, Stuart
committed
// 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())
Campbell, Stuart
committed
required = it->second;
}
Michael Whitty
committed
try
{
if (required && !Poco::File(converted).exists())
{
g_log.warning() << "Required properties path \"" << converted << "\" in the \"" << key
<< "\" variable does not exist.\n";
converted = "";
}
} catch ( Poco::FileException & )
Campbell, Stuart
committed
{
g_log.warning() << "Required properties path \"" << converted << "\" in the \"" << key
<< "\" variable does not exist.\n";
converted = "";
}
Michael Whitty
committed
Gigg, Martyn Anthony
committed
// 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.
Janik Zikovsky
committed
boost::replace_all(converted, "\\", "/");
Campbell, Stuart
committed
return 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
if (paths.empty())
return;
int options = Poco::StringTokenizer::TOK_TRIM + Poco::StringTokenizer::TOK_IGNORE_EMPTY;
Poco::StringTokenizer tokenizer(paths, ";,", options);
Poco::StringTokenizer::Iterator iend = tokenizer.end();
m_DataSearchDirs.reserve(tokenizer.count());
for (Poco::StringTokenizer::Iterator itr = tokenizer.begin(); itr != iend; ++itr)
{
m_DataSearchDirs.push_back(*itr);
}
}
/**
* 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
if (paths.empty())
return;
int options = Poco::StringTokenizer::TOK_TRIM + Poco::StringTokenizer::TOK_IGNORE_EMPTY;
Poco::StringTokenizer tokenizer(paths, ";,", options);
Poco::StringTokenizer::Iterator iend = tokenizer.end();
m_UserSearchDirs.reserve(tokenizer.count());
for (Poco::StringTokenizer::Iterator itr = tokenizer.begin(); itr != iend; ++itr)
{
m_UserSearchDirs.push_back(*itr);
}
}
/**
* The path that is passed should be as returned by makeAbsolute() and
* this function will return true if that path is in the list
Janik Zikovsky
committed
* @param path :: the absolute path name to search for
* @return true if the path was found
*/
Gigg, Martyn Anthony
committed
bool ConfigServiceImpl::isInDataSearchList(const std::string & path) const
{
Janik Zikovsky
committed
std::vector<std::string>::const_iterator it = std::find_if(m_DataSearchDirs.begin(),
m_DataSearchDirs.end(), std::bind2nd(std::equal_to<std::string>(), path));
Gigg, Martyn Anthony
committed
return (it != m_DataSearchDirs.end());
}
Campbell, Stuart
committed
/**
* writes a basic placeholder user.properties file to disk
* any errors are caught and logged, but not propagated
*/
void ConfigServiceImpl::createUserPropertiesFile() const
{
try
{
Janik Zikovsky
committed
std::fstream filestr((getUserPropertiesDir() + m_user_properties_file_name).c_str(),
std::fstream::out);
Campbell, Stuart
committed
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 << "#for example" << std::endl;
filestr
<< "#uncommenting the line below will set the number of algorithms to retain interim results for to be 90"
<< std::endl;
filestr << "#overriding any value set in the Mantid.properties file" << std::endl;
filestr << "#algorithms.retained = 90" << 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 << "#uncomment to change logging level. Valid values are: error, warning, notice, information, debug" << std::endl;
filestr << "#logging.loggers.root.level = information" << std::endl;
Campbell, Stuart
committed
filestr.close();
} catch (std::runtime_error& ex)
Campbell, Stuart
committed
{
Gigg, Martyn Anthony
committed
g_log.warning() << "Unable to write out user.properties file to " << getUserPropertiesDir()
Campbell, Stuart
committed
<< m_user_properties_file_name << " error: " << ex.what() << std::endl;
Campbell, Stuart
committed
}
Campbell, Stuart
committed
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
/**
* 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"
"logging.loggers.root.channel.channel3 = signalChannel"
"# 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;"
"# SignalChannel - Passes messages to the MantidPlot User interface"
"logging.channels.signalChannel.class = SignalChannel";
return propFile;
}
//-------------------------------
// Public member functions
//-------------------------------
/** Updates and existing configuration and restarts the logging
Janik Zikovsky
committed
* @param filename :: The filename and optionally path of the file to load
* @param append :: If false (default) then any previous configuration is discarded,
Campbell, Stuart
committed
* otherwise the new keys are added, and repeated keys will override existing ones.
Janik Zikovsky
committed
* @param update_caches :: If true(default) then the various property caches are updated
Campbell, Stuart
committed
*/
void ConfigServiceImpl::updateConfig(const std::string& filename, const bool append,
const bool update_caches)
{
loadConfig(filename, append);
//Ensure that the default save directory makes sense
if (!append)
{
std::string save_dir = getString("defaultsave.directory");
if (Poco::trimInPlace(save_dir).size() == 0)
setString("defaultsave.directory", Poco::Path::home());
Campbell, Stuart
committed
if (update_caches)
{
// Only configure logging once
configureLogging();
Campbell, Stuart
committed
//Ensure that any relative paths given in the configuration file are relative to the correct directory
convertRelativeToAbsolute();
//Configure search paths into a specially saved store as they will be used frequently
cacheDataSearchPaths();
cacheUserSearchPaths();
Campbell, Stuart
committed
}
/**
* Save the configuration to the user file
Janik Zikovsky
committed
* @param filename :: The filename for the saved configuration
* @throw std::runtime_error if the file cannot be opened
Campbell, Stuart
committed
*/
void ConfigServiceImpl::saveConfig(const std::string & filename) const
{
if (m_changed_keys.empty())
return;
Campbell, Stuart
committed
// Open and read the user properties file
std::string updated_file("");
std::ifstream reader(filename.c_str(), std::ios::in);
if (reader.bad())
Campbell, Stuart
committed
g_log.error() << "Error reading current user properties file. Cannot save updated configuration.\n";
throw std::runtime_error("Error opening user properties file. Cannot save updated configuration.");
Campbell, Stuart
committed
std::string file_line(""), output("");
bool line_continuing(false);
while (std::getline(reader, file_line))
Campbell, Stuart
committed
if (!file_line.empty())
Campbell, Stuart
committed
char last = *(file_line.end() - 1);
if (last == '\\')
// If we are not in line continuation mode then need
// a fresh start line
Janik Zikovsky
committed
if (!line_continuing)
output = "";
Campbell, Stuart
committed
line_continuing = true;
output += file_line + "\n";
Gigg, Martyn Anthony
committed
continue;
Campbell, Stuart
committed
else if (line_continuing)
Campbell, Stuart
committed
output += file_line;
line_continuing = false;
Campbell, Stuart
committed
output = file_line;
Campbell, Stuart
committed
else
Campbell, Stuart
committed
output = "";
Gigg, Martyn Anthony
committed
updated_file += "\n";
Campbell, Stuart
committed
continue;
Robert Whitley
committed
} //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
Campbell, Stuart
committed
}
Robert Whitley
committed
else
Campbell, Stuart
committed
{
Robert Whitley
committed
key = output.substr(0,pos); //Strip the equals to get only the key
Robert Whitley
committed
//Now deal with trimming (removes spaces)
Poco::trimInPlace(key);
//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 && !hasProperty(key) )
Campbell, Stuart
committed
{
Robert Whitley
committed
//Remove the key from the changed key list
m_changed_keys.erase(key);
continue;
Campbell, Stuart
committed
}
else
Robert Whitley
committed
//If it does exist perform the current actions below
std::set<std::string>::iterator iend = m_changed_keys.end();
std::set<std::string>::iterator itr = m_changed_keys.begin();
Gigg, Martyn Anthony
committed
Robert Whitley
committed
for (; itr != iend; ++itr)
{
if (output.find(*itr) != std::string::npos)
{
break;
}
}
if (itr == iend)
{
updated_file += output;
}
else
{
std::string key = *itr;
std::string value = getString(*itr, false);
updated_file += key + "=" + value;
//Remove the key from the changed key list
m_changed_keys.erase(itr);
}
updated_file += "\n";
}
} // End while-loop
Campbell, Stuart
committed
// Any remaining keys within the changed key store weren't present in the current user properties so append them
if (!m_changed_keys.empty())
Campbell, Stuart
committed
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;)
Campbell, Stuart
committed
updated_file += *key_itr + "=";
updated_file += getString(*key_itr, false);
if (++key_itr != key_end)
Campbell, Stuart
committed
updated_file += "\n";
Campbell, Stuart
committed
m_changed_keys.clear();
Campbell, Stuart
committed
// Write out the new file
std::ofstream writer(filename.c_str(), std::ios_base::trunc);
if (writer.bad())
Campbell, Stuart
committed
writer.close();
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.");
}
Campbell, Stuart
committed
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
* then the local store is searched first.
*
Janik Zikovsky
committed
* @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.
Campbell, Stuart
committed
* @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())
Campbell, Stuart
committed
return (*mitr).second;
Campbell, Stuart
committed
std::string retVal;
try
Campbell, Stuart
committed
retVal = m_pConf->getString(keyName);
if( retVal == m_removedFlag ) retVal = "";
Campbell, Stuart
committed
} catch (Poco::NotFoundException&)
Campbell, Stuart
committed
g_log.debug() << "Unable to find " << keyName << " in the properties file" << std::endl;
retVal = "";
Campbell, Stuart
committed
return retVal;
}
Robert Whitley
committed
/** 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
* a key or it couldn't be found.
*/
std::vector<std::string> ConfigServiceImpl::getKeys(const std::string& keyName) const
{
std::vector<std::string> rawKeys;
Robert Whitley
committed
std::vector<std::string> keyVector;
keyVector.reserve(rawKeys.size());
Robert Whitley
committed
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&)
{
}
keyVector.push_back(key);
}
Robert Whitley
committed
}
catch (Poco::NotFoundException&)
{
g_log.debug() << "Unable to find " << keyName << " in the properties file" << std::endl;
keyVector.clear();
}
return keyVector;
}
Robert Whitley
committed
/** Removes a key from the memory stored properties file and inserts the key into the
* changed key list so that when the program calls saveConfig the properties file will
* be the same and not contain the key no more
*
* @param keyName :: The key that is to be deleted
*/
void ConfigServiceImpl::remove(const std::string& rootName) const
{
try
{
// m_pConf->remove(rootName) will only work in Poco v >=1.4. Current Ubuntu and RHEL use 1.3.x
// Simulate removal by marking with a flag value
m_pConf->setString(rootName, m_removedFlag);
Robert Whitley
committed
}
catch (Poco::NotFoundException&)
{
g_log.debug() << "Unable to find " << rootName << " in the properties file" << std::endl;
}
m_changed_keys.insert(rootName);
}
/** Checks to see whether the given key exists.
*
* @param keyName :: The case sensitive key that you are looking to see if exists.
* @returns Boolean value denoting whether the exists or not.
*/
bool ConfigServiceImpl::hasProperty(const std::string& rootName) const
{
// Work around a limitation of Poco < v1.4 which has no remove functionality
return m_pConf->hasProperty(rootName) && m_pConf->getString(rootName) != m_removedFlag;
Robert Whitley
committed
}
/** Checks to see whether the given file target is an executable one and it exists.
* This method will expand environment variables found in the given file path.
Robert Whitley
committed
*
* @param target :: The path to the file you wish to see whether it's an executable.
Robert Whitley
committed
* @returns Boolean value denoting whether the file is an executable or not.
*/
bool ConfigServiceImpl::isExecutable(const std::string& target) const
{
try
{
std::string expTarget = Poco::Path::expand(target);
Poco::File tempFile = Poco::File(expTarget);
Robert Whitley
committed
if (tempFile.exists())
{
if(tempFile.canExecute())
return true;
else
return false;
}
else
return false;
}
catch(Poco::Exception&)
{
return false;
}
}
Robert Whitley
committed
/** Runs a command line string to open a program. The function can take program arguments.
* i.e it can load in a file to the program on startup.
*
* This method will expand environment variables found in the given file path.
*
Robert Whitley
committed
* @param programFilePath :: The directory where the program is located.
* @param programArguments :: The arguments that the program can take on startup. For example,
* the file to load up.
*/
void ConfigServiceImpl::launchProcess(const std::string& programFilePath, const std::vector<std::string>& programArguments) const
{
try
{
std::string expTarget = Poco::Path::expand(programFilePath);
Poco::Process::launch(expTarget, programArguments);
Robert Whitley
committed
}
catch(Poco::SystemException &e)
{
throw std::runtime_error(e.what());
}
}
Campbell, Stuart
committed
/**
* Set a configuration property. An existing key will have its value updated.
Janik Zikovsky
committed
* @param key :: The key to refer to this property
* @param value :: The value of the property
Campbell, Stuart
committed
*/
void ConfigServiceImpl::setString(const std::string & key, const std::string & value)
{
Michael Whitty
committed
std::string old;
try
{
old = m_pConf->getString(key);