Implement our own database ORM, and update the whole code to use it

Entirely replace LiteSQL

fix #3271
This commit is contained in:
louiz’
2017-06-14 00:19:15 +02:00
parent 7ca95a0974
commit 50cadf3dac
21 changed files with 872 additions and 326 deletions
-70
View File
@@ -1,70 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE database SYSTEM "litesql.dtd">
<database name="BibouDB" namespace="db">
<object name="GlobalOptions">
<field name="owner" type="string" length="3071"/>
<field name="maxHistoryLength" type="integer" default="20"/>
<field name="recordHistory" type="boolean" default="true"/>
<index unique="true">
<indexfield name="owner"/>
</index>
</object>
<object name="IrcServerOptions">
<field name="owner" type="string" length="3071"/>
<field name="server" type="string" length="3071"/>
<field name="pass" type="string" length="1024" default=""/>
<field name="afterConnectionCommand" type="string" length="510" default=""/>
<field name="tlsPorts" type="string" length="4096" default="6697;6670" />
<field name="ports" type="string" length="4096" default="6667" />
<field name="username" type="string" length="1024" default=""/>
<field name="realname" type="string" length="1024" default=""/>
<field name="verifyCert" type="boolean" default="true"/>
<field name="trustedFingerprint" type="string"/>
<field name="encodingOut" type="string" default="ISO-8859-1"/>
<field name="encodingIn" type="string" default="ISO-8859-1"/>
<field name="maxHistoryLength" type="integer" default="20"/>
<index unique="true">
<indexfield name="owner"/>
<indexfield name="server"/>
</index>
</object>
<object name="IrcChannelOptions">
<field name="owner" type="string" length="3071"/>
<field name="server" type="string" length="3071"/>
<field name="channel" type="string" length="1024"/>
<field name="encodingOut" type="string"/>
<field name="encodingIn" type="string"/>
<field name="maxHistoryLength" type="integer" default="20"/>
<field name="persistent" type="boolean" default="false"/>
<index unique="true">
<indexfield name="owner"/>
<indexfield name="server"/>
<indexfield name="channel"/>
</index>
</object>
<object name="MucLogLine">
<field name="uuid" type="string" length="36" />
<!-- The bare JID of the user for which we stored the line. It's
the JID associated with the Bridge -->
<field name="owner" type="string" length="4096" />
<!-- The room IID -->
<field name="ircChanName" type="string" length="4096" />
<field name="ircServerName" type="string" length="4096" />
<field name="date" type="datetime" />
<field name="body" type="string" length="65536"/>
<field name="nick" type="string" length="4096" />
</object>
</database>
+8 -8
View File
@@ -23,7 +23,7 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
#ifdef USE_DATABASE #ifdef USE_DATABASE
const auto jid = bridge.get_bare_jid(); const auto jid = bridge.get_bare_jid();
auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
return options.encodingIn.value(); return options.col<Database::EncodingIn>();
#else #else
(void)bridge; (void)bridge;
(void)iid; (void)iid;
@@ -38,7 +38,7 @@ Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Pol
{ {
#ifdef USE_DATABASE #ifdef USE_DATABASE
const auto options = Database::get_global_options(this->user_jid); const auto options = Database::get_global_options(this->user_jid);
this->set_record_history(options.recordHistory.value()); this->set_record_history(options.col<Database::RecordHistory>());
#endif #endif
} }
@@ -258,7 +258,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
#ifdef USE_DATABASE #ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(line); const auto xmpp_body = this->make_xmpp_body(line);
if (this->record_history) if (this->record_history)
uuid = Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), irc->get_own_nick()); std::get<0>(xmpp_body), irc->get_own_nick());
#endif #endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
@@ -436,7 +436,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
#ifdef USE_DATABASE #ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid,
iid.get_server(), iid.get_local()); iid.get_server(), iid.get_local());
persistent = coptions.persistent.value(); persistent = coptions.col<Database::Persistent>();
#endif #endif
if (channel->joined && !channel->parting && !persistent) if (channel->joined && !channel->parting && !persistent)
{ {
@@ -837,7 +837,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
#ifdef USE_DATABASE #ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(body, encoding); const auto xmpp_body = this->make_xmpp_body(body, encoding);
if (!nick.empty() && this->record_history) if (!nick.empty() && this->record_history)
Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), nick); std::get<0>(xmpp_body), nick);
#endif #endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
@@ -994,12 +994,12 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam
{ {
#ifdef USE_DATABASE #ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name); const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value()); const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.col<Database::MaxHistoryLength>());
chan_name.append(utils::empty_if_fixed_server("%" + hostname)); chan_name.append(utils::empty_if_fixed_server("%" + hostname));
for (const auto& line: lines) for (const auto& line: lines)
{ {
const auto seconds = line.date.value().timeStamp(); const auto seconds = line.col<Database::Date>();
this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(), this->xmpp.send_history_message(chan_name, line.col<Database::Nick>(), line.col<Database::Body>(),
this->user_jid + "/" + resource, seconds); this->user_jid + "/" + resource, seconds);
} }
#else #else
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
template <typename T>
struct Column
{
using real_type = T;
T value;
};
struct Id: Column<std::size_t> { static constexpr auto name = "id_";
static constexpr auto options = "PRIMARY KEY AUTOINCREMENT"; };
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <database/query.hpp>
#include <database/table.hpp>
#include <string>
#include <sqlite3.h>
struct CountQuery: public Query
{
CountQuery(std::string name):
Query("SELECT count(*) FROM ")
{
this->body += std::move(name);
}
std::size_t execute(sqlite3* db)
{
auto statement = this->prepare(db);
std::size_t res = 0;
if (sqlite3_step(statement) == SQLITE_ROW)
res = sqlite3_column_int64(statement, 0);
else
{
log_error("Count request didn’t return a result");
return 0;
}
if (sqlite3_step(statement) != SQLITE_DONE)
log_warning("Count request returned more than one result.");
log_debug("Returning count: ", res);
return res;
}
};
+99 -109
View File
@@ -2,175 +2,166 @@
#ifdef USE_DATABASE #ifdef USE_DATABASE
#include <database/database.hpp> #include <database/database.hpp>
#include <logger/logger.hpp>
#include <irc/iid.hpp>
#include <uuid/uuid.h> #include <uuid/uuid.h>
#include <utils/get_first_non_empty.hpp> #include <utils/get_first_non_empty.hpp>
#include <utils/time.hpp> #include <utils/time.hpp>
using namespace std::string_literals; #include <sqlite3.h>
std::unique_ptr<db::BibouDB> Database::db; sqlite3* Database::db;
Database::MucLogLineTable Database::muc_log_lines("MucLogLine_");
Database::GlobalOptionsTable Database::global_options("GlobalOptions_");
Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_");
Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_");
void Database::open(const std::string& filename, const std::string& db_type) void Database::open(const std::string& filename)
{ {
try auto res = sqlite3_open_v2(filename.data(), &Database::db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
log_debug("open: ", res);
Database::muc_log_lines.create(Database::db);
Database::global_options.create(Database::db);
Database::irc_server_options.create(Database::db);
Database::irc_channel_options.create(Database::db);
}
Database::GlobalOptions Database::get_global_options(const std::string& owner)
{
auto request = Database::global_options.select().where() << Owner{} << "=" << owner;
Database::GlobalOptions options{Database::global_options.get_name()};
auto result = request.execute(Database::db);
if (result.size() == 1)
options = result.front();
else
options.col<Owner>() = owner;
return options;
}
Database::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server)
{
auto request = Database::irc_server_options.select().where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
Database::IrcServerOptions options{Database::irc_server_options.get_name()};
auto result = request.execute(Database::db);
if (result.size() == 1)
options = result.front();
else
{ {
auto new_db = std::make_unique<db::BibouDB>(db_type, options.col<Owner>() = owner;
"database="s + filename); options.col<Server>() = server;
if (new_db->needsUpgrade())
new_db->upgrade();
Database::db = std::move(new_db);
} catch (const litesql::DatabaseError& e) {
log_error("Failed to open database ", filename, ". ", e.what());
throw;
} }
return options;
} }
void Database::set_verbose(const bool val) Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, const std::string& server, const std::string& channel)
{ {
Database::db->verbose = val; auto request = Database::irc_channel_options.select().where() << Owner{} << "=" << owner <<\
} " and " << Server{} << "=" << server <<\
" and " << Channel{} << "=" << channel;
db::GlobalOptions Database::get_global_options(const std::string& owner) Database::IrcChannelOptions options{Database::irc_channel_options.get_name()};
{ auto result = request.execute(Database::db);
try { if (result.size() == 1)
auto options = litesql::select<db::GlobalOptions>(*Database::db, options = result.front();
db::GlobalOptions::Owner == owner).one(); else
return options; {
} catch (const litesql::NotFound& e) { options.col<Owner>() = owner;
db::GlobalOptions options(*Database::db); options.col<Server>() = server;
options.owner = owner; options.col<Channel>() = channel;
return options;
} }
return options;
} }
db::IrcServerOptions Database::get_irc_server_options(const std::string& owner, Database::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server,
const std::string& server)
{
try {
auto options = litesql::select<db::IrcServerOptions>(*Database::db,
db::IrcServerOptions::Owner == owner &&
db::IrcServerOptions::Server == server).one();
return options;
} catch (const litesql::NotFound& e) {
db::IrcServerOptions options(*Database::db);
options.owner = owner;
options.server = server;
// options.update();
return options;
}
}
db::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner,
const std::string& server,
const std::string& channel)
{
try {
auto options = litesql::select<db::IrcChannelOptions>(*Database::db,
db::IrcChannelOptions::Owner == owner &&
db::IrcChannelOptions::Server == server &&
db::IrcChannelOptions::Channel == channel).one();
return options;
} catch (const litesql::NotFound& e) {
db::IrcChannelOptions options(*Database::db);
options.owner = owner;
options.server = server;
options.channel = channel;
return options;
}
}
db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner,
const std::string& server,
const std::string& channel) const std::string& channel)
{ {
auto coptions = Database::get_irc_channel_options(owner, server, channel); auto coptions = Database::get_irc_channel_options(owner, server, channel);
auto soptions = Database::get_irc_server_options(owner, server); auto soptions = Database::get_irc_server_options(owner, server);
coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(), coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
soptions.encodingIn.value()); soptions.col<EncodingIn>());
coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
soptions.encodingOut.value()); soptions.col<EncodingOut>());
coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(), coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
soptions.maxHistoryLength.value()); soptions.col<MaxHistoryLength>());
return coptions; return coptions;
} }
db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel)
const std::string& server,
const std::string& channel)
{ {
auto coptions = Database::get_irc_channel_options(owner, server, channel); auto coptions = Database::get_irc_channel_options(owner, server, channel);
auto soptions = Database::get_irc_server_options(owner, server); auto soptions = Database::get_irc_server_options(owner, server);
auto goptions = Database::get_global_options(owner); auto goptions = Database::get_global_options(owner);
coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(), coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
soptions.encodingIn.value()); soptions.col<EncodingIn>());
coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
soptions.encodingOut.value());
coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(), coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
soptions.maxHistoryLength.value(), soptions.col<EncodingOut>());
goptions.maxHistoryLength.value());
coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
soptions.col<MaxHistoryLength>(),
goptions.col<MaxHistoryLength>());
return coptions; return coptions;
} }
std::string Database::store_muc_message(const std::string& owner, const Iid& iid, std::string Database::store_muc_message(const std::string& owner, const std::string& chan_name,
Database::time_point date, const std::string& server_name, Database::time_point date,
const std::string& body, const std::string& body, const std::string& nick)
const std::string& nick)
{ {
db::MucLogLine line(*Database::db); auto line = Database::muc_log_lines.row();
auto uuid = Database::gen_uuid(); auto uuid = Database::gen_uuid();
line.uuid = uuid; line.col<Uuid>() = uuid;
line.owner = owner; line.col<Owner>() = owner;
line.ircChanName = iid.get_local(); line.col<IrcChanName>() = chan_name;
line.ircServerName = iid.get_server(); line.col<IrcServerName>() = server_name;
line.date = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count(); line.col<Date>() = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
line.body = body; line.col<Body>() = body;
line.nick = nick; line.col<Nick>() = nick;
line.update(); line.save(Database::db);
return uuid; return uuid;
} }
std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
int limit, const std::string& start, const std::string& end) int limit, const std::string& start, const std::string& end)
{ {
auto request = litesql::select<db::MucLogLine>(*Database::db, auto request = Database::muc_log_lines.select().where() << Database::Owner{} << "=" << owner << \
db::MucLogLine::Owner == owner && " and " << Database::IrcChanName{} << "=" << chan_name << \
db::MucLogLine::IrcChanName == chan_name && " and " << Database::IrcServerName{} << "=" << server;
db::MucLogLine::IrcServerName == server);
request.orderBy(db::MucLogLine::Id, false);
if (limit >= 0)
request.limit(limit);
if (!start.empty()) if (!start.empty())
{ {
const auto start_time = utils::parse_datetime(start); const auto start_time = utils::parse_datetime(start);
if (start_time != -1) if (start_time != -1)
request.where(db::MucLogLine::Date >= start_time); request << " and " << Database::Date{} << ">=" << start_time;
} }
if (!end.empty()) if (!end.empty())
{ {
const auto end_time = utils::parse_datetime(end); const auto end_time = utils::parse_datetime(end);
if (end_time != -1) if (end_time != -1)
request.where(db::MucLogLine::Date <= end_time); request << " and " << Database::Date{} << "<=" << end_time;
} }
const auto& res = request.all();
return {res.crbegin(), res.crend()}; request.order_by() << Id{} << " DESC ";
if (limit >= 0)
request.limit() << limit;
auto result = request.execute(Database::db);
return {result.crbegin(), result.crend()};
} }
void Database::close() void Database::close()
{ {
Database::db.reset(nullptr); sqlite3_close_v2(Database::db);
} }
std::string Database::gen_uuid() std::string Database::gen_uuid()
@@ -182,5 +173,4 @@ std::string Database::gen_uuid()
return uuid_str; return uuid_str;
} }
#endif #endif
+107 -30
View File
@@ -1,22 +1,101 @@
#pragma once #pragma once
#include <biboumi.h> #include <biboumi.h>
#ifdef USE_DATABASE #ifdef USE_DATABASE
#include "biboudb.hpp" #include <database/table.hpp>
#include <database/column.hpp>
#include <database/count_query.hpp>
#include <chrono>
#include <string>
#include <memory> #include <memory>
#include <litesql.hpp>
#include <chrono>
class Iid;
class Database class Database
{ {
public: public:
using time_point = std::chrono::system_clock::time_point; using time_point = std::chrono::system_clock::time_point;
struct Uuid: Column<std::string> { static constexpr auto name = "uuid_";
static constexpr auto options = ""; };
struct Owner: Column<std::string> { static constexpr auto name = "owner_";
static constexpr auto options = ""; };
struct IrcChanName: Column<std::string> { static constexpr auto name = "ircChanName_";
static constexpr auto options = ""; };
struct Channel: Column<std::string> { static constexpr auto name = "channel_";
static constexpr auto options = ""; };
struct IrcServerName: Column<std::string> { static constexpr auto name = "ircServerName_";
static constexpr auto options = ""; };
struct Server: Column<std::string> { static constexpr auto name = "server_";
static constexpr auto options = ""; };
struct Date: Column<time_point::rep> { static constexpr auto name = "date_";
static constexpr auto options = ""; };
struct Body: Column<std::string> { static constexpr auto name = "body_";
static constexpr auto options = ""; };
struct Nick: Column<std::string> { static constexpr auto name = "nick_";
static constexpr auto options = ""; };
struct Pass: Column<std::string> { static constexpr auto name = "pass_";
static constexpr auto options = ""; };
struct Ports: Column<std::string> { static constexpr auto name = "tlsPorts_";
static constexpr auto options = ""; };
struct TlsPorts: Column<std::string> { static constexpr auto name = "ports_";
static constexpr auto options = ""; };
struct Username: Column<std::string> { static constexpr auto name = "username_";
static constexpr auto options = ""; };
struct Realname: Column<std::string> { static constexpr auto name = "realname_";
static constexpr auto options = ""; };
struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterConnectionCommand_";
static constexpr auto options = ""; };
struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedFingerprint_";
static constexpr auto options = ""; };
struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingOut_";
static constexpr auto options = ""; };
struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingIn_";
static constexpr auto options = ""; };
struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxHistoryLength_";
static constexpr auto options = ""; };
struct RecordHistory: Column<bool> { static constexpr auto name = "recordHistory_";
static constexpr auto options = ""; };
struct VerifyCert: Column<bool> { static constexpr auto name = "verifyCert_";
static constexpr auto options = ""; };
struct Persistent: Column<bool> { static constexpr auto name = "persistent_";
static constexpr auto options = ""; };
using MucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, Date, Body, Nick>;
using MucLogLine = MucLogLineTable::RowType;
using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory>;
using GlobalOptions = GlobalOptionsTable::RowType;
using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, AfterConnectionCommand, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength>;
using IrcServerOptions = IrcServerOptionsTable::RowType;
using IrcChannelOptionsTable = Table<Id, Owner, Server, Channel, EncodingOut, EncodingIn, MaxHistoryLength, Persistent>;
using IrcChannelOptions = IrcChannelOptionsTable::RowType;
Database() = default; Database() = default;
~Database() = default; ~Database() = default;
@@ -25,42 +104,40 @@ public:
Database& operator=(const Database&) = delete; Database& operator=(const Database&) = delete;
Database& operator=(Database&&) = delete; Database& operator=(Database&&) = delete;
static void set_verbose(const bool val); static GlobalOptions get_global_options(const std::string& owner);
static IrcServerOptions get_irc_server_options(const std::string& owner,
template<typename PersistentType>
static size_t count()
{
return litesql::select<PersistentType>(*Database::db).count();
}
/**
* Return the object from the db. Create it beforehand (with all default
* values) if it is not already present.
*/
static db::GlobalOptions get_global_options(const std::string& owner);
static db::IrcServerOptions get_irc_server_options(const std::string& owner,
const std::string& server); const std::string& server);
static db::IrcChannelOptions get_irc_channel_options(const std::string& owner, static IrcChannelOptions get_irc_channel_options(const std::string& owner,
const std::string& server, const std::string& server,
const std::string& channel); const std::string& channel);
static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner, static IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner,
const std::string& server, const std::string& server,
const std::string& channel); const std::string& channel);
static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner, static IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner,
const std::string& server, const std::string& server,
const std::string& channel); const std::string& channel);
static std::vector<db::MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, static std::vector<MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
int limit=-1, const std::string& start="", const std::string& end=""); int limit=-1, const std::string& start="", const std::string& end="");
static std::string store_muc_message(const std::string& owner, const Iid& iid, static std::string store_muc_message(const std::string& owner, const std::string& chan_name, const std::string& server_name,
time_point date, const std::string& body, const std::string& nick); time_point date, const std::string& body, const std::string& nick);
static void close(); static void close();
static void open(const std::string& filename, const std::string& db_type="sqlite3"); static void open(const std::string& filename);
template <typename TableType>
static std::size_t count(const TableType& table)
{
CountQuery query{table.get_name()};
return query.execute(Database::db);
}
private: static MucLogLineTable muc_log_lines;
static GlobalOptionsTable global_options;
static IrcServerOptionsTable irc_server_options;
static IrcChannelOptionsTable irc_channel_options;
static sqlite3* db;
private:
static std::string gen_uuid(); static std::string gen_uuid();
static std::unique_ptr<db::BibouDB> db;
}; };
#endif /* USE_DATABASE */ #endif /* USE_DATABASE */
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include <database/column.hpp>
#include <database/query.hpp>
#include <logger/logger.hpp>
#include <type_traits>
#include <vector>
#include <string>
#include <tuple>
#include <sqlite3.h>
template <int N, typename ColumnType, typename... T>
typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
actual_bind(sqlite3_stmt* statement, std::vector<std::string>& params, const std::tuple<T...>&)
{
const auto value = params.front();
params.erase(params.begin());
if (sqlite3_bind_text(statement, N + 1, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT) != SQLITE_OK)
log_error("Failed to bind ", value, " to param ", N);
else
log_debug("Bound (not id) [", value, "] to ", N);
}
template <int N, typename ColumnType, typename... T>
typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
actual_bind(sqlite3_stmt* statement, std::vector<std::string>&, const std::tuple<T...>& columns)
{
auto&& column = std::get<Id>(columns);
if (column.value != 0)
{
if (sqlite3_bind_int64(statement, N + 1, column.value) != SQLITE_OK)
log_error("Failed to bind ", column.value, " to id.");
}
else if (sqlite3_bind_null(statement, N + 1) != SQLITE_OK)
log_error("Failed to bind NULL to param ", N);
else
log_debug("Bound NULL to ", N);
}
struct InsertQuery: public Query
{
InsertQuery(const std::string& name):
Query("INSERT OR REPLACE INTO ")
{
this->body += name;
}
template <typename... T>
void execute(const std::tuple<T...>& columns, sqlite3* db)
{
auto statement = this->prepare(db);
{
this->bind_param<0>(columns, statement);
if (sqlite3_step(statement) != SQLITE_DONE)
log_error("Failed to execute query: ", sqlite3_errmsg(db));
}
}
template <int N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
bind_param(const std::tuple<T...>& columns, sqlite3_stmt* statement)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
actual_bind<N, ColumnType>(statement, this->params, columns);
this->bind_param<N+1>(columns, statement);
}
template <int N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
bind_param(const std::tuple<T...>&, sqlite3_stmt*)
{}
template <typename... T>
void insert_values(const std::tuple<T...>& columns)
{
this->body += "VALUES (";
this->insert_value<0>(columns);
this->body += ")";
}
template <int N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
insert_value(const std::tuple<T...>& columns)
{
this->body += "?";
if (N != sizeof...(T) - 1)
this->body += ",";
this->body += " ";
add_param(*this, std::get<N>(columns));
this->insert_value<N+1>(columns);
}
template <int N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
insert_value(const std::tuple<T...>&)
{ }
template <typename... T>
void insert_col_names(const std::tuple<T...>& columns)
{
this->body += " (";
this->insert_col_name<0>(columns);
this->body += ")\n";
}
template <int N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
insert_col_name(const std::tuple<T...>& columns)
{
auto value = std::get<N>(columns);
this->body += value.name;
if (N < (sizeof...(T) - 1))
this->body += ", ";
this->insert_col_name<N+1>(columns);
}
template <int N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
insert_col_name(const std::tuple<T...>&)
{}
private:
};
+11
View File
@@ -0,0 +1,11 @@
#include <database/query.hpp>
#include <database/column.hpp>
template <>
void add_param<Id>(Query&, const Id&)
{}
void actual_add_param(Query& query, const std::string& val)
{
query.params.push_back(val);
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <logger/logger.hpp>
#include <vector>
#include <string>
#include <sqlite3.h>
struct Query
{
std::string body;
std::vector<std::string> params;
Query(std::string str):
body(std::move(str))
{}
sqlite3_stmt* prepare(sqlite3* db)
{
sqlite3_stmt* statement;
log_debug(this->body);
auto res = sqlite3_prepare(db, this->body.data(), static_cast<int>(this->body.size()) + 1,
&statement, nullptr);
if (res != SQLITE_OK)
{
log_error("Error preparing statement: ", sqlite3_errmsg(db));
return nullptr;
}
return statement;
}
};
template <typename ColumnType>
void add_param(Query& query, const ColumnType& column)
{
actual_add_param(query, column.value);
}
template <typename T>
void actual_add_param(Query& query, const T& val)
{
query.params.push_back(std::to_string(val));
}
void actual_add_param(Query& query, const std::string& val);
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <database/insert_query.hpp>
#include <logger/logger.hpp>
#include <type_traits>
#include <sqlite3.h>
template <typename ColumnType, typename... T>
typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
update_id(std::tuple<T...>&, sqlite3*)
{}
template <typename ColumnType, typename... T>
typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
update_id(std::tuple<T...>& columns, sqlite3* db)
{
auto&& column = std::get<ColumnType>(columns);
log_debug("Found an autoincrement col.");
auto res = sqlite3_last_insert_rowid(db);
log_debug("Value is now: ", res);
column.value = res;
}
template <std::size_t N, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>& columns, sqlite3* db)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
update_id<ColumnType>(columns, db);
update_autoincrement_id<N+1>(columns, db);
}
template <std::size_t N, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
update_autoincrement_id(std::tuple<T...>&, sqlite3*)
{}
template <typename... T>
struct Row
{
Row(std::string name):
table_name(std::move(name))
{}
template <typename Type>
auto& col()
{
auto&& col = std::get<Type>(this->columns);
return col.value;
}
template <typename Type>
const auto& col() const
{
auto&& col = std::get<Type>(this->columns);
return col.value;
}
void save(sqlite3* db)
{
InsertQuery query(this->table_name);
query.insert_col_names(this->columns);
query.insert_values(this->columns);
log_debug(query.body);
query.execute(this->columns, db);
update_autoincrement_id<0>(this->columns, db);
}
std::tuple<T...> columns;
std::string table_name;
};
+153
View File
@@ -0,0 +1,153 @@
#pragma once
#include <database/query.hpp>
#include <logger/logger.hpp>
#include <database/row.hpp>
#include <vector>
#include <string>
#include <sqlite3.h>
using namespace std::string_literals;
template <typename T>
typename std::enable_if<std::is_integral<T>::value, sqlite3_int64>::type
extract_row_value(sqlite3_stmt* statement, const int i)
{
return sqlite3_column_int64(statement, i);
}
template <typename T>
typename std::enable_if<std::is_same<std::string, T>::value, std::string>::type
extract_row_value(sqlite3_stmt* statement, const int i)
{
const auto size = sqlite3_column_bytes(statement, i);
const unsigned char* str = sqlite3_column_text(statement, i);
std::string result(reinterpret_cast<const char*>(str), size);
return result;
}
template <std::size_t N=0, typename... T>
typename std::enable_if<N < sizeof...(T), void>::type
extract_row_values(Row<T...>& row, sqlite3_stmt* statement)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(row.columns))>::type;
auto&& column = std::get<N>(row.columns);
column.value = static_cast<decltype(column.value)>(extract_row_value<typename ColumnType::real_type>(statement, N));
extract_row_values<N+1>(row, statement);
}
template <std::size_t N=0, typename... T>
typename std::enable_if<N == sizeof...(T), void>::type
extract_row_values(Row<T...>&, sqlite3_stmt*)
{}
template <typename... T>
struct SelectQuery: public Query
{
SelectQuery(std::string table_name):
Query("SELECT"),
table_name(table_name)
{
this->insert_col_name<0>();
this->body += " from " + this->table_name;
}
template <std::size_t N>
typename std::enable_if<N < sizeof...(T), void>::type
insert_col_name()
{
using ColumnsType = std::tuple<T...>;
ColumnsType tuple{};
auto value = std::get<N>(tuple);
this->body += " "s + value.name;
if (N < (sizeof...(T) - 1))
this->body += ", ";
this->insert_col_name<N+1>();
}
template <std::size_t N>
typename std::enable_if<N == sizeof...(T), void>::type
insert_col_name()
{}
SelectQuery& where()
{
this->body += " WHERE ";
return *this;
};
SelectQuery& order_by()
{
this->body += " ORDER BY ";
return *this;
}
SelectQuery& limit()
{
this->body += " LIMIT ";
return *this;
}
auto execute(sqlite3* db)
{
auto statement = this->prepare(db);
int i = 1;
for (const std::string& param: this->params)
{
if (sqlite3_bind_text(statement, i, param.data(), static_cast<int>(param.size()), SQLITE_TRANSIENT) != SQLITE_OK)
log_debug("Failed to bind ", param, " to param ", i);
else
log_debug("Bound ", param, " to ", i);
i++;
}
std::vector<Row<T...>> rows;
while (sqlite3_step(statement) == SQLITE_ROW)
{
Row<T...> row(this->table_name);
extract_row_values(row, statement);
rows.push_back(row);
}
return rows;
}
const std::string table_name;
};
template <typename T, typename... ColumnTypes>
typename std::enable_if<!std::is_integral<T>::value, SelectQuery<ColumnTypes...>&>::type
operator<<(SelectQuery<ColumnTypes...>& query, const T&)
{
query.body += T::name;
return query;
}
template <typename... ColumnTypes>
SelectQuery<ColumnTypes...>& operator<<(SelectQuery<ColumnTypes...>& query, const char* str)
{
query.body += str;
return query;
}
template <typename... ColumnTypes>
SelectQuery<ColumnTypes...>& operator<<(SelectQuery<ColumnTypes...>& query, const std::string& str)
{
query.body += "?";
actual_add_param(query, str);
return query;
}
template <typename Integer, typename... ColumnTypes>
typename std::enable_if<std::is_integral<Integer>::value, SelectQuery<ColumnTypes...>&>::type
operator<<(SelectQuery<ColumnTypes...>& query, const Integer& i)
{
query.body += "?";
actual_add_param(query, i);
return query;
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include <database/select_query.hpp>
#include <database/type_to_sql.hpp>
#include <logger/logger.hpp>
#include <database/row.hpp>
#include <algorithm>
#include <string>
template <typename... T>
class Table
{
static_assert(sizeof...(T) > 0, "Table cannot be empty");
using ColumnTypes = std::tuple<T...>;
public:
using RowType = Row<T...>;
Table(std::string name):
name(std::move(name))
{}
void create(sqlite3* db)
{
std::string res{"CREATE TABLE IF NOT EXISTS "};
res += this->name;
res += " (\n";
this->add_column_create<0>(res);
res += ")";
log_debug(res);
char* error;
const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error);
log_debug("result: ", +result);
if (result != SQLITE_OK)
{
log_error("Error executing query: ", error);
sqlite3_free(error);
}
}
RowType row()
{
return {this->name};
}
SelectQuery<T...> select()
{
SelectQuery<T...> select(this->name);
return select;
}
const std::string& get_name() const
{
return this->name;
}
private:
template <std::size_t N>
typename std::enable_if<N < sizeof...(T), void>::type
add_column_create(std::string& str)
{
using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
using RealType = typename ColumnType::real_type;
str += ColumnType::name;
str += " ";
str += TypeToSQLType<RealType>::type;
str += " "s + ColumnType::options;
if (N != sizeof...(T) - 1)
str += ",";
str += "\n";
add_column_create<N+1>(str);
}
template <std::size_t N>
typename std::enable_if<N == sizeof...(T), void>::type
add_column_create(std::string&)
{ }
const std::string name;
};
+7
View File
@@ -0,0 +1,7 @@
#include <database/type_to_sql.hpp>
template <> const std::string TypeToSQLType<int>::type = "INTEGER";
template <> const std::string TypeToSQLType<std::size_t>::type = "INTEGER";
template <> const std::string TypeToSQLType<long>::type = "INTEGER";
template <> const std::string TypeToSQLType<bool>::type = "INTEGER";
template <> const std::string TypeToSQLType<std::string>::type = "TEXT";
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <string>
template <typename T>
struct TypeToSQLType { static const std::string type; };
+12 -12
View File
@@ -156,11 +156,11 @@ IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
#ifdef USE_DATABASE #ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname()); this->get_hostname());
std::vector<std::string> ports = utils::split(options.ports, ';', false); std::vector<std::string> ports = utils::split(options.col<Database::Ports>(), ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it) for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, false); this->ports_to_try.emplace(*it, false);
# ifdef BOTAN_FOUND # ifdef BOTAN_FOUND
ports = utils::split(options.tlsPorts, ';', false); ports = utils::split(options.col<Database::TlsPorts>(), ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it) for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, true); this->ports_to_try.emplace(*it, true);
# endif // BOTAN_FOUND # endif // BOTAN_FOUND
@@ -204,7 +204,7 @@ void IrcClient::start()
# ifdef USE_DATABASE # ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname()); this->get_hostname());
this->credential_manager.set_trusted_fingerprint(options.trustedFingerprint); this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
# endif # endif
#endif #endif
this->connect(this->hostname, port, tls); this->connect(this->hostname, port, tls);
@@ -275,8 +275,8 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE #ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname()); this->get_hostname());
if (!options.pass.value().empty()) if (!options.col<Database::Pass>().empty())
this->send_pass_command(options.pass.value()); this->send_pass_command(options.col<Database::Pass>());
#endif #endif
this->send_nick_command(this->current_nick); this->send_nick_command(this->current_nick);
@@ -284,10 +284,10 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE #ifdef USE_DATABASE
if (Config::get("realname_customization", "true") == "true") if (Config::get("realname_customization", "true") == "true")
{ {
if (!options.username.value().empty()) if (!options.col<Database::Username>().empty())
this->username = options.username.value(); this->username = options.col<Database::Username>();
if (!options.realname.value().empty()) if (!options.col<Database::Realname>().empty())
this->realname = options.realname.value(); this->realname = options.col<Database::Realname>();
this->send_user_command(username, realname); this->send_user_command(username, realname);
} }
else else
@@ -894,8 +894,8 @@ void IrcClient::on_welcome_message(const IrcMessage& message)
#ifdef USE_DATABASE #ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname()); this->get_hostname());
if (!options.afterConnectionCommand.value().empty()) if (!options.col<Database::AfterConnectionCommand>().empty())
this->send_raw(options.afterConnectionCommand.value()); this->send_raw(options.col<Database::AfterConnectionCommand>());
#endif #endif
// Install a repeated events to regularly send a PING // Install a repeated events to regularly send a PING
TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this), TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
@@ -1260,7 +1260,7 @@ bool IrcClient::abort_on_invalid_cert() const
{ {
#ifdef USE_DATABASE #ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname); auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname);
return options.verifyCert.value(); return options.col<Database::VerifyCert>();
#endif #endif
return true; return true;
} }
+1 -4
View File
@@ -12,9 +12,6 @@
#include <atomic> #include <atomic>
#include <csignal> #include <csignal>
#ifdef USE_DATABASE
# include <litesql.hpp>
#endif
#include <identd/identd_server.hpp> #include <identd/identd_server.hpp>
@@ -91,7 +88,7 @@ int main(int ac, char** av)
#ifdef USE_DATABASE #ifdef USE_DATABASE
try { try {
open_database(); open_database();
} catch (const litesql::DatabaseError&) { } catch (...) {
return 1; return 1;
} }
#endif #endif
+1 -1
View File
@@ -26,7 +26,7 @@ void reload_process()
#ifdef USE_DATABASE #ifdef USE_DATABASE
try { try {
open_database(); open_database();
} catch (const litesql::DatabaseError&) { } catch (...) {
log_warning("Re-using the previous database."); log_warning("Re-using the previous database.");
} }
#endif #endif
+43 -45
View File
@@ -130,7 +130,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
{ {
XmlSubNode value(max_histo_length, "value"); XmlSubNode value(max_histo_length, "value");
value.set_inner(std::to_string(options.maxHistoryLength.value())); value.set_inner(std::to_string(options.col<Database::MaxHistoryLength>()));
} }
XmlSubNode record_history(x, "field"); XmlSubNode record_history(x, "field");
@@ -142,7 +142,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
{ {
XmlSubNode value(record_history, "value"); XmlSubNode value(record_history, "value");
value.set_name("value"); value.set_name("value");
if (options.recordHistory.value()) if (options.col<Database::RecordHistory>())
value.set_inner("true"); value.set_inner("true");
else else
value.set_inner("false"); value.set_inner("false");
@@ -164,18 +164,18 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session,
if (field->get_tag("var") == "max_history_length" && if (field->get_tag("var") == "max_history_length" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.maxHistoryLength = value->get_inner(); options.col<Database::MaxHistoryLength>() = atoi(value->get_inner().data());
else if (field->get_tag("var") == "record_history" && else if (field->get_tag("var") == "record_history" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
{ {
options.recordHistory = to_bool(value->get_inner()); options.col<Database::RecordHistory>() = to_bool(value->get_inner());
Bridge* bridge = biboumi_component.find_user_bridge(owner.bare()); Bridge* bridge = biboumi_component.find_user_bridge(owner.bare());
if (bridge) if (bridge)
bridge->set_record_history(options.recordHistory.value()); bridge->set_record_history(options.col<Database::RecordHistory>());
} }
} }
options.update(); options.save(Database::db);
command_node.delete_all_children(); command_node.delete_all_children();
XmlSubNode note(command_node, "note"); XmlSubNode note(command_node, "note");
@@ -211,8 +211,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
ports["type"] = "text-multi"; ports["type"] = "text-multi";
ports["label"] = "Ports"; ports["label"] = "Ports";
ports["desc"] = "List of ports to try, without TLS. Defaults: 6667."; ports["desc"] = "List of ports to try, without TLS. Defaults: 6667.";
auto vals = utils::split(options.ports.value(), ';', false); for (const auto& val: utils::split(options.col<Database::Ports>(), ';', false))
for (const auto& val: vals)
{ {
XmlSubNode ports_value(ports, "value"); XmlSubNode ports_value(ports, "value");
ports_value.set_inner(val); ports_value.set_inner(val);
@@ -224,8 +223,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
tls_ports["type"] = "text-multi"; tls_ports["type"] = "text-multi";
tls_ports["label"] = "TLS ports"; tls_ports["label"] = "TLS ports";
tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670."; tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670.";
vals = utils::split(options.tlsPorts.value(), ';', false); for (const auto& val: utils::split(options.col<Database::TlsPorts>(), ';', false))
for (const auto& val: vals)
{ {
XmlSubNode tls_ports_value(tls_ports, "value"); XmlSubNode tls_ports_value(tls_ports, "value");
tls_ports_value.set_inner(val); tls_ports_value.set_inner(val);
@@ -237,7 +235,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
verify_cert["label"] = "Verify certificate"; verify_cert["label"] = "Verify certificate";
verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid"; verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid";
XmlSubNode verify_cert_value(verify_cert, "value"); XmlSubNode verify_cert_value(verify_cert, "value");
if (options.verifyCert.value()) if (options.col<Database::VerifyCert>())
verify_cert_value.set_inner("true"); verify_cert_value.set_inner("true");
else else
verify_cert_value.set_inner("false"); verify_cert_value.set_inner("false");
@@ -246,10 +244,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
fingerprint["var"] = "fingerprint"; fingerprint["var"] = "fingerprint";
fingerprint["type"] = "text-single"; fingerprint["type"] = "text-single";
fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust."; fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust.";
if (!options.trustedFingerprint.value().empty()) if (!options.col<Database::TrustedFingerprint>().empty())
{ {
XmlSubNode fingerprint_value(fingerprint, "value"); XmlSubNode fingerprint_value(fingerprint, "value");
fingerprint_value.set_inner(options.trustedFingerprint.value()); fingerprint_value.set_inner(options.col<Database::TrustedFingerprint>());
} }
#endif #endif
@@ -258,10 +256,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
pass["type"] = "text-private"; pass["type"] = "text-private";
pass["label"] = "Server password"; pass["label"] = "Server password";
pass["desc"] = "Will be used in a PASS command when connecting"; pass["desc"] = "Will be used in a PASS command when connecting";
if (!options.pass.value().empty()) if (!options.col<Database::Pass>().empty())
{ {
XmlSubNode pass_value(pass, "value"); XmlSubNode pass_value(pass, "value");
pass_value.set_inner(options.pass.value()); pass_value.set_inner(options.col<Database::Pass>());
} }
XmlSubNode after_cnt_cmd(x, "field"); XmlSubNode after_cnt_cmd(x, "field");
@@ -269,10 +267,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
after_cnt_cmd["type"] = "text-single"; after_cnt_cmd["type"] = "text-single";
after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server."; after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server.";
after_cnt_cmd["label"] = "After-connection IRC command"; after_cnt_cmd["label"] = "After-connection IRC command";
if (!options.afterConnectionCommand.value().empty()) if (!options.col<Database::AfterConnectionCommand>().empty())
{ {
XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value"); XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value");
after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value()); after_cnt_cmd_value.set_inner(options.col<Database::AfterConnectionCommand>());
} }
if (Config::get("realname_customization", "true") == "true") if (Config::get("realname_customization", "true") == "true")
@@ -281,20 +279,20 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
username["var"] = "username"; username["var"] = "username";
username["type"] = "text-single"; username["type"] = "text-single";
username["label"] = "Username"; username["label"] = "Username";
if (!options.username.value().empty()) if (!options.col<Database::Username>().empty())
{ {
XmlSubNode username_value(username, "value"); XmlSubNode username_value(username, "value");
username_value.set_inner(options.username.value()); username_value.set_inner(options.col<Database::Username>());
} }
XmlSubNode realname(x, "field"); XmlSubNode realname(x, "field");
realname["var"] = "realname"; realname["var"] = "realname";
realname["type"] = "text-single"; realname["type"] = "text-single";
realname["label"] = "Realname"; realname["label"] = "Realname";
if (!options.realname.value().empty()) if (!options.col<Database::Realname>().empty())
{ {
XmlSubNode realname_value(realname, "value"); XmlSubNode realname_value(realname, "value");
realname_value.set_inner(options.realname.value()); realname_value.set_inner(options.col<Database::Realname>());
} }
} }
@@ -303,10 +301,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
encoding_out["type"] = "text-single"; encoding_out["type"] = "text-single";
encoding_out["desc"] = "The encoding used when sending messages to the IRC server."; encoding_out["desc"] = "The encoding used when sending messages to the IRC server.";
encoding_out["label"] = "Out encoding"; encoding_out["label"] = "Out encoding";
if (!options.encodingOut.value().empty()) if (!options.col<Database::EncodingOut>().empty())
{ {
XmlSubNode encoding_out_value(encoding_out, "value"); XmlSubNode encoding_out_value(encoding_out, "value");
encoding_out_value.set_inner(options.encodingOut.value()); encoding_out_value.set_inner(options.col<Database::EncodingOut>());
} }
XmlSubNode encoding_in(x, "field"); XmlSubNode encoding_in(x, "field");
@@ -314,10 +312,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
encoding_in["type"] = "text-single"; encoding_in["type"] = "text-single";
encoding_in["desc"] = "The encoding used to decode message received from the IRC server."; encoding_in["desc"] = "The encoding used to decode message received from the IRC server.";
encoding_in["label"] = "In encoding"; encoding_in["label"] = "In encoding";
if (!options.encodingIn.value().empty()) if (!options.col<Database::EncodingIn>().empty())
{ {
XmlSubNode encoding_in_value(encoding_in, "value"); XmlSubNode encoding_in_value(encoding_in, "value");
encoding_in_value.set_inner(options.encodingIn.value()); encoding_in_value.set_inner(options.col<Database::EncodingIn>());
} }
} }
@@ -342,7 +340,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
std::string ports; std::string ports;
for (const auto& val: values) for (const auto& val: values)
ports += val->get_inner() + ";"; ports += val->get_inner() + ";";
options.ports = ports; options.col<Database::Ports>() = ports;
} }
#ifdef BOTAN_FOUND #ifdef BOTAN_FOUND
@@ -351,31 +349,31 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
std::string ports; std::string ports;
for (const auto& val: values) for (const auto& val: values)
ports += val->get_inner() + ";"; ports += val->get_inner() + ";";
options.tlsPorts = ports; options.col<Database::TlsPorts>() = ports;
} }
else if (field->get_tag("var") == "verify_cert" && value else if (field->get_tag("var") == "verify_cert" && value
&& !value->get_inner().empty()) && !value->get_inner().empty())
{ {
auto val = to_bool(value->get_inner()); auto val = to_bool(value->get_inner());
options.verifyCert = val; options.col<Database::VerifyCert>() = val;
} }
else if (field->get_tag("var") == "fingerprint" && value && else if (field->get_tag("var") == "fingerprint" && value &&
!value->get_inner().empty()) !value->get_inner().empty())
{ {
options.trustedFingerprint = value->get_inner(); options.col<Database::TrustedFingerprint>() = value->get_inner();
} }
#endif // BOTAN_FOUND #endif // BOTAN_FOUND
else if (field->get_tag("var") == "pass" && else if (field->get_tag("var") == "pass" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.pass = value->get_inner(); options.col<Database::Pass>() = value->get_inner();
else if (field->get_tag("var") == "after_connect_command" && else if (field->get_tag("var") == "after_connect_command" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.afterConnectionCommand = value->get_inner(); options.col<Database::AfterConnectionCommand>() = value->get_inner();
else if (field->get_tag("var") == "username" && else if (field->get_tag("var") == "username" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
@@ -383,24 +381,24 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
auto username = value->get_inner(); auto username = value->get_inner();
// The username must not contain spaces // The username must not contain spaces
std::replace(username.begin(), username.end(), ' ', '_'); std::replace(username.begin(), username.end(), ' ', '_');
options.username = username; options.col<Database::Username>() = username;
} }
else if (field->get_tag("var") == "realname" && else if (field->get_tag("var") == "realname" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.realname = value->get_inner(); options.col<Database::Realname>() = value->get_inner();
else if (field->get_tag("var") == "encoding_out" && else if (field->get_tag("var") == "encoding_out" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.encodingOut = value->get_inner(); options.col<Database::EncodingOut>() = value->get_inner();
else if (field->get_tag("var") == "encoding_in" && else if (field->get_tag("var") == "encoding_in" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.encodingIn = value->get_inner(); options.col<Database::EncodingIn>() = value->get_inner();
} }
options.update(); options.save(Database::db);
command_node.delete_all_children(); command_node.delete_all_children();
XmlSubNode note(command_node, "note"); XmlSubNode note(command_node, "note");
@@ -441,10 +439,10 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
encoding_out["type"] = "text-single"; encoding_out["type"] = "text-single";
encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel"; encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel";
encoding_out["label"] = "Out encoding"; encoding_out["label"] = "Out encoding";
if (!options.encodingOut.value().empty()) if (!options.col<Database::EncodingOut>().empty())
{ {
XmlSubNode encoding_out_value(encoding_out, "value"); XmlSubNode encoding_out_value(encoding_out, "value");
encoding_out_value.set_inner(options.encodingOut.value()); encoding_out_value.set_inner(options.col<Database::EncodingOut>());
} }
XmlSubNode encoding_in(x, "field"); XmlSubNode encoding_in(x, "field");
@@ -452,10 +450,10 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
encoding_in["type"] = "text-single"; encoding_in["type"] = "text-single";
encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel"; encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel";
encoding_in["label"] = "In encoding"; encoding_in["label"] = "In encoding";
if (!options.encodingIn.value().empty()) if (!options.col<Database::EncodingIn>().empty())
{ {
XmlSubNode encoding_in_value(encoding_in, "value"); XmlSubNode encoding_in_value(encoding_in, "value");
encoding_in_value.set_inner(options.encodingIn.value()); encoding_in_value.set_inner(options.col<Database::EncodingIn>());
} }
XmlSubNode persistent(x, "field"); XmlSubNode persistent(x, "field");
@@ -466,7 +464,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
{ {
XmlSubNode value(persistent, "value"); XmlSubNode value(persistent, "value");
value.set_name("value"); value.set_name("value");
if (options.persistent.value()) if (options.col<Database::Persistent>())
value.set_inner("true"); value.set_inner("true");
else else
value.set_inner("false"); value.set_inner("false");
@@ -510,18 +508,18 @@ bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& reque
if (field->get_tag("var") == "encoding_out" && if (field->get_tag("var") == "encoding_out" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.encodingOut = value->get_inner(); options.col<Database::EncodingOut>() = value->get_inner();
else if (field->get_tag("var") == "encoding_in" && else if (field->get_tag("var") == "encoding_in" &&
value && !value->get_inner().empty()) value && !value->get_inner().empty())
options.encodingIn = value->get_inner(); options.col<Database::EncodingIn>() = value->get_inner();
else if (field->get_tag("var") == "persistent" && else if (field->get_tag("var") == "persistent" &&
value) value)
options.persistent = to_bool(value->get_inner()); options.col<Database::Persistent>() = to_bool(value->get_inner());
} }
options.update(); options.save(Database::db);
} }
return true; return true;
} }
+7 -9
View File
@@ -18,8 +18,6 @@
#include <biboumi.h> #include <biboumi.h>
#include <uuid/uuid.h>
#ifdef SYSTEMD_FOUND #ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h> # include <systemd/sd-daemon.h>
#endif #endif
@@ -648,9 +646,9 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
limit = 100; limit = 100;
} }
const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end);
for (const db::MucLogLine& line: lines) for (const Database::MucLogLine& line: lines)
{ {
if (!line.nick.value().empty()) if (!line.col<Database::Nick>().empty())
this->send_archived_message(line, to.full(), from.full(), query_id); this->send_archived_message(line, to.full(), from.full(), query_id);
} }
this->send_iq_result_full_jid(id, from.full(), to.full()); this->send_iq_result_full_jid(id, from.full(), to.full());
@@ -659,7 +657,7 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
return false; return false;
} }
void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to, void BiboumiComponent::send_archived_message(const Database::MucLogLine& log_line, const std::string& from, const std::string& to,
const std::string& queryid) const std::string& queryid)
{ {
Stanza message("message"); Stanza message("message");
@@ -671,22 +669,22 @@ void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, con
result["xmlns"] = MAM_NS; result["xmlns"] = MAM_NS;
if (!queryid.empty()) if (!queryid.empty())
result["queryid"] = queryid; result["queryid"] = queryid;
result["id"] = log_line.uuid.value(); result["id"] = log_line.col<Database::Uuid>();
XmlSubNode forwarded(result, "forwarded"); XmlSubNode forwarded(result, "forwarded");
forwarded["xmlns"] = FORWARD_NS; forwarded["xmlns"] = FORWARD_NS;
XmlSubNode delay(forwarded, "delay"); XmlSubNode delay(forwarded, "delay");
delay["xmlns"] = DELAY_NS; delay["xmlns"] = DELAY_NS;
delay["stamp"] = utils::to_string(log_line.date.value().timeStamp()); delay["stamp"] = utils::to_string(log_line.col<Database::Date>());
XmlSubNode submessage(forwarded, "message"); XmlSubNode submessage(forwarded, "message");
submessage["xmlns"] = CLIENT_NS; submessage["xmlns"] = CLIENT_NS;
submessage["from"] = from + "/" + log_line.nick.value(); submessage["from"] = from + "/" + log_line.col<Database::Nick>();
submessage["type"] = "groupchat"; submessage["type"] = "groupchat";
XmlSubNode body(submessage, "body"); XmlSubNode body(submessage, "body");
body.set_inner(log_line.body.value()); body.set_inner(log_line.col<Database::Body>());
} }
this->send_stanza(message); this->send_stanza(message);
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <database/database.hpp>
#include <xmpp/xmpp_component.hpp> #include <xmpp/xmpp_component.hpp>
#include <xmpp/jid.hpp> #include <xmpp/jid.hpp>
@@ -96,7 +96,7 @@ public:
#ifdef USE_DATABASE #ifdef USE_DATABASE
bool handle_mam_request(const Stanza& stanza); bool handle_mam_request(const Stanza& stanza);
void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to, void send_archived_message(const Database::MucLogLine& log_line, const std::string& from, const std::string& to,
const std::string& queryid); const std::string& queryid);
bool handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id); bool handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id);
bool handle_room_configuration_form(const XmlNode& query, const std::string& from, const Jid& to, const std::string& id); bool handle_room_configuration_form(const XmlNode& query, const std::string& from, const Jid& to, const std::string& id);
+22 -24
View File
@@ -8,24 +8,22 @@ TEST_CASE("Database")
{ {
#ifdef USE_DATABASE #ifdef USE_DATABASE
Database::open(":memory:"); Database::open(":memory:");
Database::set_verbose(false);
SECTION("Basic retrieve and update") SECTION("Basic retrieve and update")
{ {
auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com");
o.update(); o.save(Database::db);
auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com");
auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com");
// b does not yet exist in the db, the object is created but not yet // b does not yet exist in the db, the object is created but not yet
// inserted // inserted
CHECK(1 == Database::count<db::IrcServerOptions>()); CHECK(1 == Database::count(Database::irc_server_options));
b.update(); b.save(Database::db);
CHECK(2 == Database::count<db::IrcServerOptions>()); CHECK(2 == Database::count(Database::irc_server_options));
CHECK(b.pass == ""); CHECK(b.col<Database::Pass>() == "");
CHECK(b.pass.value() == "");
} }
SECTION("channel options") SECTION("channel options")
@@ -33,16 +31,16 @@ TEST_CASE("Database")
Config::set("db_name", ":memory:"); Config::set("db_name", ":memory:");
auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo");
CHECK(o.encodingIn == ""); CHECK(o.col<Database::EncodingIn>() == "");
o.encodingIn = "ISO-8859-1"; o.col<Database::EncodingIn>() = "ISO-8859-1";
o.update(); o.save(Database::db);
auto b = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); auto b = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo");
CHECK(o.encodingIn == "ISO-8859-1"); CHECK(o.col<Database::EncodingIn>() == "ISO-8859-1");
} }
SECTION("Channel options with server default") SECTION("Channel options with server default")
{ {
const std::string owner{"zouzou@example.com"}; const std::string owner{"CACA@example.com"};
const std::string server{"irc.example.com"}; const std::string server{"irc.example.com"};
const std::string chan1{"#foo"}; const std::string chan1{"#foo"};
@@ -51,43 +49,43 @@ TEST_CASE("Database")
GIVEN("An option defined for the channel but not the server") GIVEN("An option defined for the channel but not the server")
{ {
c.encodingIn = "channelEncoding"; c.col<Database::EncodingIn>() = "channelEncoding";
c.update(); c.save(Database::db);
WHEN("we fetch that option") WHEN("we fetch that option")
{ {
auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1);
THEN("we get the channel option") THEN("we get the channel option")
CHECK(r.encodingIn == "channelEncoding"); CHECK(r.col<Database::EncodingIn>() == "channelEncoding");
} }
} }
GIVEN("An option defined for the server but not the channel") GIVEN("An option defined for the server but not the channel")
{ {
s.encodingIn = "serverEncoding"; s.col<Database::EncodingIn>() = "serverEncoding";
s.update(); s.save(Database::db);
WHEN("we fetch that option") WHEN("we fetch that option")
{ {
auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1);
THEN("we get the server option") THEN("we get the server option")
CHECK(r.encodingIn == "serverEncoding"); CHECK(r.col<Database::EncodingIn>() == "serverEncoding");
} }
} }
GIVEN("An option defined for both the server and the channel") GIVEN("An option defined for both the server and the channel")
{ {
s.encodingIn = "serverEncoding"; s.col<Database::EncodingIn>() = "serverEncoding";
s.update(); s.save(Database::db);
c.encodingIn = "channelEncoding"; c.col<Database::EncodingIn>() = "channelEncoding";
c.update(); c.save(Database::db);
WHEN("we fetch that option") WHEN("we fetch that option")
{ {
auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1);
THEN("we get the channel option") THEN("we get the channel option")
CHECK(r.encodingIn == "channelEncoding"); CHECK(r.col<Database::EncodingIn>() == "channelEncoding");
} }
WHEN("we fetch that option, with no channel specified") WHEN("we fetch that option, with no channel specified")
{ {
auto r = Database::get_irc_channel_options_with_server_default(owner, server, ""); auto r = Database::get_irc_channel_options_with_server_default(owner, server, "");
THEN("we get the server option") THEN("we get the server option")
CHECK(r.encodingIn == "serverEncoding"); CHECK(r.col<Database::EncodingIn>() == "serverEncoding");
} }
} }
} }