~singpolyma/biboumi

f0d9273da61ce154dbe460cf58c98de851d30615 — Florent Le Coz 9 years ago 10d5287
Add a Config module, and use it to get the password from a file
5 files changed, 304 insertions(+), 7 deletions(-)

M CMakeLists.txt
A src/config/config.cpp
A src/config/config.hpp
M src/main.cpp
M src/test.cpp
M CMakeLists.txt => CMakeLists.txt +16 -2
@@ 33,6 33,14 @@ add_library(utils STATIC ${source_utils})
target_link_libraries(utils ${ICONV_LIBRARIES})

#
## config
#
file(GLOB source_config
  src/config/*.[hc]pp)
add_library(config STATIC ${source_config})
target_link_libraries(config utils)

#
## network
#
file(GLOB source_network


@@ 64,11 72,16 @@ file(GLOB source_bridge
add_library(bridge STATIC ${source_bridge})
target_link_libraries(bridge xmpp irc)

#
## Main executable
#
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME}
  xmpp
  irc
  bridge)
  bridge
  utils
  config)

#
## Tests


@@ 79,6 92,7 @@ target_link_libraries(test
  xmpp
  irc
  bridge
  utils)
  utils
  config)

CONFIGURE_FILE(config.h.cmake src/config.h @ONLY)

A src/config/config.cpp => src/config/config.cpp +129 -0
@@ 0,0 1,129 @@
#include <utils/make_unique.hpp>
#include <config/config.hpp>

#include <iostream>
#include <sstream>

std::string Config::filename = "./biboumi.cfg";
bool Config::file_must_exist = false;

std::string Config::get(const std::string& option, const std::string& def)
{
  Config* self = Config::instance().get();
  auto it = self->values.find(option);

  if (it == self->values.end())
    return def;
  return it->second;
}

int Config::get_int(const std::string& option, const int& def)
{
  Config* self = Config::instance().get();
  std::string res = self->get(option, "");
  if (!res.empty())
    return atoi(res.c_str());
  else
    return def;
}

void Config::set_int(const std::string& option, const int& value, bool save)
{
  std::ostringstream os;
  os << value;
  Config::set(option, os.str(), save);
}

void Config::set(const std::string& option, const std::string& value, bool save)
{
  Config* self = Config::instance().get();
  self->values[option] = value;
  if (save)
    {
      self->save_to_file();
      self->trigger_configuration_change();
    }
}

void Config::connect(t_config_changed_callback callback)
{
  Config* self = Config::instance().get();
  self->callbacks.push_back(callback);
}

void Config::close()
{
  Config* self = Config::instance().get();
  self->save_to_file();
  self->values.clear();
  Config::instance().reset();
}

/**
 * Private methods
 */

void Config::trigger_configuration_change()
{
  std::vector<t_config_changed_callback>::iterator it;
  for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it)
      (*it)();
}

std::unique_ptr<Config>& Config::instance()
{
  static std::unique_ptr<Config> instance;

  if (!instance)
    {
      instance = std::make_unique<Config>();
      instance->read_conf();
    }
  return instance;
}

bool Config::read_conf()
{
  std::ifstream file;
  file.open(filename.data());
  if (!file.is_open())
    {
      if (Config::file_must_exist)
        {
          perror(("Error while opening file " + filename + " for reading.").c_str());
          file.exceptions(std::ifstream::failbit);
        }
      return false;
    }

  std::string line;
  size_t pos;
  std::string option;
  std::string value;
  while (file.good())
    {
      std::getline(file, line);
      if (line == "" || line[0] == '#')
        continue ;
      pos = line.find('=');
      if (pos == std::string::npos)
        continue ;
      option = line.substr(0, pos);
      value = line.substr(pos+1);
      this->values[option] = value;
    }
  return true;
}

void Config::save_to_file() const
{
  std::ofstream file(this->filename.data());
  if (file.fail())
    {
      std::cerr << "Could not save config file." << std::endl;
      return ;
    }
  for (auto& it: this->values)
    file << it.first << "=" << it.second << std::endl;
  file.close();
}

A src/config/config.hpp => src/config/config.hpp +111 -0
@@ 0,0 1,111 @@
/**
 * Read the config file and save all the values in a map.
 * Also, a singleton.
 *
 * Use Config::filename = "bla" to set the filename you want to use.
 *
 * If you want to exit if the file does not exist when it is open for
 * reading, set Config::file_must_exist = true.
 *
 * Config::get() can the be used to access the values in the conf.
 *
 * Use Config::close() when you're done getting/setting value. This will
 * save the config into the file.
 */

#ifndef CONFIG_INCLUDED
# define CONFIG_INCLUDED

#include <functional>
#include <fstream>
#include <memory>
#include <vector>
#include <string>
#include <map>

typedef std::function<void()> t_config_changed_callback;

class Config
{
public:
  Config(){};
  ~Config(){};
  /**
   * returns a value from the config. If it doesn’t exist, use
   * the second argument as the default.
   * @param option The option we want
   * @param def The default value in case the option does not exist
   */
  static std::string get(const std::string&, const std::string&);
  /**
   * returns a value from the config. If it doesn’t exist, use
   * the second argument as the default.
   * @param option The option we want
   * @param def The default value in case the option does not exist
   */
  static int get_int(const std::string&, const int&);
  /**
   * Set a value for the given option. And write all the config
   * in the file from which it was read if boolean is set.
   * @param option The option to set
   * @param value The value to use
   * @param save if true, save the config file
   */
  static void set(const std::string&, const std::string&, bool save = false);
  /**
   * Set a value for the given option. And write all the config
   * in the file from which it was read if boolean is set.
   * @param option The option to set
   * @param value The value to use
   * @param save if true, save the config file
   */
  static void set_int(const std::string&, const int&, bool save = false);
  /**
   * Adds a function to a list. This function will be called whenever a
   * configuration change occurs.
   */
  static void connect(t_config_changed_callback);
  /**
   * Close the config file, saving it to the file is save == true.
   */
  static void close();

  /**
   * Set the value of the filename to use, before calling any method.
   */
  static std::string filename;
  /**
   * Set to true if you want an exception to be raised if the file does not
   * exist when reading it.
   */
  static bool file_must_exist;

private:
  /**
   * Get the singleton instance
   */
  static std::unique_ptr<Config>& instance();
  /**
   * Read the configuration file at the given path.
   */
  bool read_conf();
  /**
   * Write all the config values into the configuration file
   */
  void save_to_file() const;
  /**
   * Call all the callbacks previously registered using connect().
   * This is used to notify any class that a configuration change occured.
   */
  void trigger_configuration_change();

  std::map<std::string, std::string> values;
  std::vector<t_config_changed_callback> callbacks;

  Config(const Config&) = delete;
  Config& operator=(const Config&) = delete;
  Config(Config&&) = delete;
  Config& operator=(Config&&) = delete;
};

#endif // CONFIG_INCLUDED

M src/main.cpp => src/main.cpp +23 -4
@@ 1,13 1,32 @@
#include <network/poller.hpp>
#include <xmpp/xmpp_component.hpp>
#include <network/poller.hpp>
#include <config/config.hpp>

#include <iostream>
#include <memory>

int main()
int main(int ac, char** av)
{
  Poller p;
  if (ac > 1)
    Config::filename = av[1];
  Config::file_must_exist = true;

  std::string password;
  try { // The file must exist
    password = Config::get("password", "");
  }
  catch (const std::ios::failure& e) {
    return 1;
  }
  if (password.empty())
    {
      std::cerr << "No password provided." << std::endl;
      return 1;
    }
  std::shared_ptr<XmppComponent> xmpp_component =
    std::make_shared<XmppComponent>("irc.localhost", "secret");
    std::make_shared<XmppComponent>("irc.abricot", password);

  Poller p;
  p.add_socket_handler(xmpp_component);
  xmpp_component->start();
  while (p.poll())

M src/test.cpp => src/test.cpp +25 -1
@@ 10,6 10,8 @@
#include <utils/encoding.hpp>
#include <string.h>

#include <config/config.hpp>

#include <xmpp/xmpp_parser.hpp>

int main()


@@ 49,7 51,7 @@ int main()
   * XML parsing
   */
  XmppParser xml;
  const std::string doc = "<stream xmlns='stream_ns'><stanza b='c'>inner<child1/><child2 xmlns='child2_ns'/>tail</stanza></stream>";
  const std::string doc = "<stream xmlns='stream_ns'><stanza b='c'>inner<child1><grandchild/></child1><child2 xmlns='child2_ns'/>tail</stanza></stream>";
  xml.add_stanza_callback([](const Stanza& stanza)
      {
        assert(stanza.get_name() == "stream_ns:stanza");


@@ 62,5 64,27 @@ int main()
        assert(stanza.get_child("child2_ns:child2")->get_tail() == "tail");
      });
  xml.feed(doc.data(), doc.size(), true);

  /**
   * Config
   */
  Config::filename = "test.cfg";
  Config::file_must_exist = false;
  Config::set("coucou", "bonjour");
  Config::close();

  bool error = false;
  try
    {
      Config::file_must_exist = true;
      assert(Config::get("coucou", "") == "bonjour");
      assert(Config::get("does not exist", "default") == "default");
      Config::close();
    }
  catch (const std::ios::failure& e)
    {
      error = true;
    }
  assert(error == false);
  return 0;
}